/* * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. * Distributed under the terms of the MIT License. */ #include "main_window/SchedulingPage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "Array.h" #include "chart/NanotimeChartAxisLegendSource.h" #include "chart/LegendChartAxis.h" #include "chart/StringChartLegend.h" #include "HeaderView.h" #include "Model.h" #include "util/TimeUtils.h" static const float kThreadNameMargin = 3.0f; static const float kViewSeparationMargin = 1.0f; enum { MSG_SCHEDULING_FILTER_HIDE_SELECTED = 'schs', MSG_SCHEDULING_FILTER_HIDE_UNSELECTED = 'schu', MSG_SCHEDULING_FILTER_SHOW_ALL = 'scsa' }; enum { IO_SCHEDULING_STATE_IDLE, IO_SCHEDULING_STATE_PENDING_REQUEST, IO_SCHEDULING_STATE_PENDING_OPERATION }; struct MainWindow::SchedulingPage::SchedulingEvent { nanotime_t time; Model::ThreadWaitObjectGroup* waitObject; ThreadState state; SchedulingEvent(nanotime_t time, ThreadState state, Model::ThreadWaitObjectGroup* waitObject) : time(time), waitObject(waitObject), state(state) { } }; struct MainWindow::SchedulingPage::IOSchedulingEvent { nanotime_t time; uint32 state; IOSchedulingEvent(nanotime_t time, uint32 state) : time(time), state(state) { } }; class MainWindow::SchedulingPage::SchedulingData { public: SchedulingData() : fModel(NULL), fDataArrays(NULL), fIODataArrays(NULL), fRecordingEnabled(false) { } status_t InitCheck() const { return fDataArrays != NULL && fIODataArrays != NULL ? B_OK : B_NO_MEMORY; } void SetModel(Model* model) { delete[] fDataArrays; delete[] fIODataArrays; fModel = model; fDataArrays = NULL; fIODataArrays = NULL; if (fModel != NULL) { int32 threadCount = fModel->CountThreads(); fDataArrays = new(std::nothrow) DataArray[threadCount]; fIODataArrays = new(std::nothrow) IODataArray[threadCount]; } } void SetRecordingEnabled(bool enabled) { fRecordingEnabled = enabled; } void Clear() { int32 count = fModel->CountThreads(); if (fDataArrays != NULL) { for (int32 i = 0; i < count; i++) fDataArrays[i].Clear(); } if (fIODataArrays != NULL) { for (int32 i = 0; i < count; i++) fIODataArrays[i].Clear(); } } const Array& EventsForThread(int32 index) { return fDataArrays[index]; } const Array& IOEventsForThread(int32 index) { return fIODataArrays[index]; } void AddState(Model::Thread* thread, nanotime_t time, ThreadState state, Model::ThreadWaitObjectGroup* waitObject) { DataArray& array = fDataArrays[thread->Index()]; if (!array.IsEmpty()) { SchedulingEvent& lastEvent = array[array.Size() - 1]; if (fRecordingEnabled) { if (lastEvent.state == state && lastEvent.waitObject == waitObject) { return; } } else { // recording not enabled yet -- overwrite the last event lastEvent = SchedulingEvent(time, state, waitObject); return; } } SchedulingEvent event(time, state, waitObject); array.Add(event); } void AddIOState(Model::Thread* thread, nanotime_t time, uint32 state) { IODataArray& array = fIODataArrays[thread->Index()]; array.Add(IOSchedulingEvent(time, state)); } void AddRun(Model::Thread* thread, nanotime_t time) { AddState(thread, time, RUNNING, NULL); } void AddLatency(Model::Thread* thread, nanotime_t time) { AddState(thread, time, READY, NULL); } void AddPreemption(Model::Thread* thread, nanotime_t time) { AddState(thread, time, PREEMPTED, NULL); } void AddWait(Model::Thread* thread, nanotime_t time, Model::ThreadWaitObjectGroup* waitObject) { AddState(thread, time, WAITING, waitObject); } void AddUnspecifiedWait(Model::Thread* thread, nanotime_t time) { AddState(thread, time, WAITING, NULL); } private: typedef Array DataArray; typedef Array IODataArray; private: Model* fModel; DataArray* fDataArrays; IODataArray* fIODataArrays; bool fRecordingEnabled; }; struct MainWindow::SchedulingPage::TimeRange : BReferenceable { nanotime_t startTime; nanotime_t endTime; TimeRange(nanotime_t startTime, nanotime_t endTime) : startTime(startTime), endTime(endTime) { } }; class MainWindow::SchedulingPage::TimelineHeaderRenderer : public HeaderRenderer { public: TimelineHeaderRenderer() : fAxis(new NanotimeChartAxisLegendSource, new StringChartLegendRenderer) { fAxis.SetLocation(CHART_AXIS_TOP); } virtual float HeaderHeight(BView* view, const Header* header) { _SetupAxis(view, header); return fAxis.PreferredSize(view, view->Frame().Size()).height; } virtual float PreferredHeaderWidth(BView* view, const Header* header) { _SetupAxis(view, header); return fAxis.PreferredSize(view, view->Frame().Size()).width; } virtual void DrawHeader(BView* view, BRect frame, BRect updateRect, const Header* header, uint32 flags) { _SetupAxis(view, header); fAxis.SetFrame(frame); DrawHeaderBackground(view, frame, updateRect, flags); fAxis.Render(view, updateRect); } private: void _SetupAxis(BView* view, const Header* header) { BVariant value; if (header->GetValue(value)) { TimeRange* timeRange = dynamic_cast( value.ToReferenceable()); if (timeRange != NULL) { ChartDataRange range = ChartDataRange(timeRange->startTime, timeRange->endTime); if (range != fRange) { fAxis.SetRange(range); fRange = range; } } } } private: LegendChartAxis fAxis; ChartDataRange fRange; }; class MainWindow::SchedulingPage::BaseView : public BView { public: BaseView(const char* name, FontInfo& fontInfo, ListSelectionModel* filterModel, uint32 flags = 0) : BView(name, flags), fModel(NULL), fFilterModel(filterModel), fFontInfo(fontInfo) { } virtual void SetModel(Model* model) { fModel = model; InvalidateLayout(); } protected: int32 CountLines() const { return fFilterModel->CountSelectedItems(); } float TotalHeight() const { return fFontInfo.lineHeight * CountLines(); } int32 LineAt(BPoint point) const { int32 line = (int32)point.y / (int32)fFontInfo.lineHeight; return line < CountLines() ? line : -1; } void GetLineRange(BRect rect, int32& minLine, int32& maxLine) const { int32 lineHeight = (int32)fFontInfo.lineHeight; minLine = (int32)rect.top / lineHeight; maxLine = ((int32)ceilf(rect.bottom) + lineHeight - 1) / lineHeight; minLine = std::max(minLine, (int32)0); maxLine = std::min(maxLine, CountLines() - 1); } BRect LineRect(uint32 line) const { float y = (float)line * fFontInfo.lineHeight; return BRect(0, y, Bounds().right, y + fFontInfo.lineHeight - 1); } protected: Model* fModel; ListSelectionModel* fFilterModel; FontInfo& fFontInfo; }; class MainWindow::SchedulingPage::LineBaseView : public BaseView, protected ListSelectionModel::Listener { public: LineBaseView(const char* name, FontInfo& fontInfo, ListSelectionModel* filterModel, ListSelectionModel* selectionModel) : BaseView(name, fontInfo, filterModel, B_WILL_DRAW), fSelectionModel(selectionModel), fTextColor(ui_color(B_DOCUMENT_TEXT_COLOR)), fSelectedTextColor(fTextColor), fBackgroundColor(ui_color(B_DOCUMENT_BACKGROUND_COLOR)), fSelectedBackgroundColor(tint_color(fBackgroundColor, B_DARKEN_2_TINT)) { fFilterModel->AddListener(this); fSelectionModel->AddListener(this); } void RemoveListeners() { fSelectionModel->RemoveListener(this); fFilterModel->RemoveListener(this); } virtual void MessageReceived(BMessage* message) { switch (message->what) { case MSG_SCHEDULING_FILTER_HIDE_SELECTED: { int32 filteredCount = fFilterModel->CountSelectedItems(); int32 selectedCount = fSelectionModel->CountSelectedItems(); if (selectedCount == 0 || selectedCount == filteredCount) break; // Set the filter model to the unselected items. ListSelectionModel tempModel; for (int32 i = 0; i < filteredCount; i++) { if (!fSelectionModel->IsItemSelected(i)) { tempModel.SelectItem(fFilterModel->SelectedItemAt(i), true); } } fSelectionModel->Clear(); *fFilterModel = tempModel; Invalidate(); break; } case MSG_SCHEDULING_FILTER_HIDE_UNSELECTED: { int32 filteredCount = fFilterModel->CountSelectedItems(); int32 selectedCount = fSelectionModel->CountSelectedItems(); if (selectedCount == 0 || selectedCount == filteredCount) break; // Set the filter model to the selected items. // There might already be a filter applied, so we have to // build the new filter model manually. ListSelectionModel tempModel; for (int32 i = 0; i < selectedCount; i++) { int32 index = fFilterModel->SelectedItemAt( fSelectionModel->SelectedItemAt(i)); if (index >= 0) tempModel.SelectItem(index, true); } fSelectionModel->Clear(); *fFilterModel = tempModel; Invalidate(); break; } case MSG_SCHEDULING_FILTER_SHOW_ALL: { int32 threadCount = fModel->CountThreads(); if (fFilterModel->CountSelectedItems() == threadCount) break; // unset the filter ListSelectionModel tempModel = *fFilterModel; fSelectionModel->Clear(); fFilterModel->SelectItems(0, threadCount, false); *fSelectionModel = tempModel; Invalidate(); break; } default: BaseView::MessageReceived(message); break; } } virtual void MouseDown(BPoint where) { // get buttons and modifiers BMessage* message = Looper()->CurrentMessage(); if (message == NULL) return; int32 buttons; if (message->FindInt32("buttons", &buttons) != B_OK) return; int32 modifiers; if (message->FindInt32("modifiers", &modifiers) != B_OK) modifiers = 0; // update selection int32 line = LineAt(where); if (line >= 0) { if ((modifiers & B_SHIFT_KEY) != 0) { int32 selectedLines = fSelectionModel->CountSelectedItems(); if (selectedLines > 0) { int32 firstLine = fSelectionModel->SelectedItemAt(0); int32 lastLine = fSelectionModel->SelectedItemAt( selectedLines - 1); firstLine = std::min(firstLine, line); lastLine = std::max(lastLine, line); fSelectionModel->SelectItems(firstLine, lastLine - firstLine + 1, false); } else fSelectionModel->SelectItem(line, true); } else { if (fSelectionModel->IsItemSelected(line)) { if ((modifiers & B_COMMAND_KEY) != 0) fSelectionModel->DeselectItem(line); } else { fSelectionModel->SelectItem(line, (modifiers & B_COMMAND_KEY) != 0); } } } else fSelectionModel->Clear(); // on right mouse button open context menu if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0 && fModel != NULL) { BPopUpMenu* contextMenu = new BPopUpMenu("scheduling context menu", false, false); int32 filteredCount = fFilterModel->CountSelectedItems(); int32 selectedCount = fSelectionModel->CountSelectedItems(); BMenuItem* item = new BMenuItem("Hide selected threads", new BMessage(MSG_SCHEDULING_FILTER_HIDE_SELECTED)); contextMenu->AddItem(item); item->SetTarget(this); if (selectedCount == 0 || selectedCount == filteredCount) item->SetEnabled(false); item = new BMenuItem("Hide unselected threads", new BMessage(MSG_SCHEDULING_FILTER_HIDE_UNSELECTED)); contextMenu->AddItem(item); item->SetTarget(this); if (selectedCount == 0 || selectedCount == filteredCount) item->SetEnabled(false); item = new BMenuItem("Show all threads", new BMessage(MSG_SCHEDULING_FILTER_SHOW_ALL)); contextMenu->AddItem(item); item->SetTarget(this); if (filteredCount == fModel->CountThreads()) item->SetEnabled(false); BPoint screenWhere = ConvertToScreen(where); BRect mouseRect(screenWhere, screenWhere); mouseRect.InsetBy(-4.0, -4.0); contextMenu->Go(screenWhere, true, false, mouseRect, true); } } virtual void ItemsSelected(ListSelectionModel* model, int32 index, int32 count) { if (model == fFilterModel) Invalidate(); else InvalidateLines(index, count); } virtual void ItemsDeselected(ListSelectionModel* model, int32 index, int32 count) { if (model == fFilterModel) Invalidate(); else InvalidateLines(index, count); } void InvalidateLines(int32 index, int32 count) { float top = (float)index * fFontInfo.lineHeight; float bottom = (float)(index + count) * fFontInfo.lineHeight - 1; BRect bounds(Bounds()); Invalidate(BRect(bounds.left, top, bounds.right, bottom)); } protected: ListSelectionModel* fSelectionModel; rgb_color fTextColor; rgb_color fSelectedTextColor; rgb_color fBackgroundColor; rgb_color fSelectedBackgroundColor; }; class MainWindow::SchedulingPage::ThreadsView : public LineBaseView { public: ThreadsView(FontInfo& fontInfo, ListSelectionModel* filterModel, ListSelectionModel* selectionModel) : LineBaseView("threads", fontInfo, filterModel, selectionModel) { SetViewColor(B_TRANSPARENT_32_BIT); } ~ThreadsView() { } virtual void Draw(BRect updateRect) { if (fModel == NULL) return; // get the lines intersecting with the update rect int32 minLine, maxLine; GetLineRange(updateRect, minLine, maxLine); for (int32 i = minLine; i <= maxLine; i++) { // draw the line background BRect lineRect = LineRect(i); if (fSelectionModel->IsItemSelected(i)) { SetLowColor(fSelectedBackgroundColor); SetHighColor(fTextColor); } else { SetLowColor(fBackgroundColor); SetHighColor(fSelectedTextColor); } FillRect(lineRect, B_SOLID_LOW); Model::Thread* thread = fModel->ThreadAt( fFilterModel->SelectedItemAt(i)); if (thread == NULL) continue; // compose name string BString name = thread->Name(); name << " (" << thread->ID() << ")"; // draw the string float y = lineRect.bottom - fFontInfo.fontHeight.descent + 1; DrawString(name, BPoint(kThreadNameMargin, y)); } BRect bounds(Bounds()); BRect lowerRect(0, (float)CountLines() * fFontInfo.lineHeight, bounds.Width(), bounds.Height()); if (lowerRect.Intersects(updateRect)) { SetHighColor(fBackgroundColor); FillRect(lowerRect); } } virtual BSize MinSize() { return BSize(100, TotalHeight()); } virtual BSize PreferredSize() { return BSize(250, TotalHeight()); } virtual BSize MaxSize() { return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); } }; class MainWindow::SchedulingPage::SchedulingView : public LineBaseView { public: struct Listener { virtual ~Listener() { } virtual void DataWidthChanged() = 0; virtual void DataRangeChanged() = 0; }; private: enum { COLOR_RUNNING = 0, COLOR_PREEMPTED, COLOR_READY, COLOR_IO_REQUEST, COLOR_IO_OPERATION, ACTIVITY_COLOR_COUNT }; public: SchedulingView(FontInfo& fontInfo, ListSelectionModel* filterModel, ListSelectionModel* selectionModel) : LineBaseView("scheduling", fontInfo, filterModel, selectionModel), fStartTime(0), fEndTime(0), fNSecsPerPixel(1000000), fLastMousePos(-1, -1), fListener(NULL) { fActivityColors[COLOR_RUNNING].set_to(0, 255, 0); fActivityColors[COLOR_PREEMPTED].set_to(255, 127, 0); fActivityColors[COLOR_READY].set_to(255, 0, 0); fActivityColors[COLOR_IO_REQUEST].set_to(127, 127, 255); fActivityColors[COLOR_IO_OPERATION].set_to(0, 0, 200); fActivitySelectedColors[COLOR_RUNNING] = tint_color( fActivityColors[COLOR_RUNNING], B_DARKEN_2_TINT); fActivitySelectedColors[COLOR_PREEMPTED] = tint_color( fActivityColors[COLOR_PREEMPTED], B_DARKEN_2_TINT); fActivitySelectedColors[COLOR_READY] = tint_color( fActivityColors[COLOR_READY], B_DARKEN_2_TINT); fActivitySelectedColors[COLOR_IO_REQUEST] = tint_color( fActivityColors[COLOR_IO_REQUEST], B_DARKEN_2_TINT); fActivitySelectedColors[COLOR_IO_OPERATION] = tint_color( fActivityColors[COLOR_IO_OPERATION], B_DARKEN_2_TINT); } ~SchedulingView() { } virtual void SetModel(Model* model) { LineBaseView::SetModel(model); fSchedulingData.SetModel(model); fStartTime = 0; fEndTime = 0; if (fListener != NULL) { fListener->DataWidthChanged(); fListener->DataRangeChanged(); } } void SetListener(Listener* listener) { fListener = listener; } void UpdateScrollBar() { float width = Frame().Width(); float dataWidth = std::max(width, MinSize().width); if (BScrollBar* scrollBar = ScrollBar(B_HORIZONTAL)) { float range = dataWidth - width; if (range > 0) { scrollBar->SetRange(0, range); scrollBar->SetProportion((width + 1) / (dataWidth + 1)); scrollBar->SetSteps(fFontInfo.lineHeight, width + 1); } else { scrollBar->SetRange(0, 0); scrollBar->SetProportion(1); } } } void GetDataRange(nanotime_t& _startTime, nanotime_t& _endTime) { _GetEventTimeRange(_startTime, _endTime); } virtual BSize MinSize() { nanotime_t timeSpan = fModel != NULL ? fModel->LastEventTime() : 0; float width = std::max(float(timeSpan / fNSecsPerPixel), 100.0f); return BSize(width, TotalHeight()); } virtual BSize MaxSize() { return BSize(MinSize().width, B_SIZE_UNLIMITED); } virtual void ScrollTo(BPoint where) { LineBaseView::ScrollTo(where); fStartTime = 0; fEndTime = 0; if (fListener != NULL) fListener->DataRangeChanged(); } virtual void MessageReceived(BMessage* message) { switch (message->what) { case B_MOUSE_WHEEL_CHANGED: { // We're only interested in Shift + vertical wheel. float deltaY; if ((modifiers() & B_SHIFT_KEY) == 0 || message->FindFloat("be:wheel_delta_y", &deltaY) != B_OK) { break; } _Zoom(fLastMousePos.x, deltaY); return; } } LineBaseView::MessageReceived(message); } virtual void MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage) { fLastMousePos = where - LeftTop(); // if (fDraggingStartPos.x < 0) // return; // // ScrollBar(B_HORIZONTAL)->SetValue(fDraggingStartScrollValue // + fDraggingStartPos.x - where.x); } virtual bool GetToolTipAt(BPoint point, BToolTip** _tip) { Model::Thread* thread; nanotime_t time; _GetThreadAndTimeAt(point, thread, time); if (thread == NULL) return false; // get the thread's state ThreadState threadState = UNKNOWN; nanotime_t threadStateTime = time; nanotime_t threadStateEndTime = time; Model::ThreadWaitObjectGroup* threadWaitObject = NULL; { const Array& events = fSchedulingData.EventsForThread(thread->Index()); int32 eventCount = events.Size(); int32 lower = 0; int32 upper = eventCount - 1; while (lower < upper) { int32 mid = (lower + upper + 1) / 2; const SchedulingEvent& event = events[mid]; if (event.time > time) upper = mid - 1; else lower = mid; } if (lower >= 0 && lower < eventCount) { threadState = events[lower].state; threadStateTime = events[lower].time; threadWaitObject = events[lower].waitObject; if (lower + 1 < eventCount) threadStateEndTime = events[lower + 1].time; else threadStateEndTime = fModel->LastEventTime(); } } // get the thread's I/O state BObjectList ioRequests; _GetIORequests(thread, time, ioRequests); // find out which threads are running system_profiler_event_header** events = fModel->Events(); size_t eventIndex = fModel->ClosestEventIndex(time + 1); // find the first event after the one we're interested in; we'll // skip it in the loop int32 cpuCount = fModel->CountCPUs(); int32 missingThreads = cpuCount; Model::Thread* runningThreads[cpuCount]; memset(runningThreads, 0, sizeof(Model::Thread*) * cpuCount); while (missingThreads > 0 && eventIndex > 0) { eventIndex--; system_profiler_event_header* header = events[eventIndex]; if (header->event != B_SYSTEM_PROFILER_THREAD_SCHEDULED || runningThreads[header->cpu] != NULL) { continue; } system_profiler_thread_scheduled* event = (system_profiler_thread_scheduled*)(header + 1); if (Model::Thread* thread = fModel->ThreadByID(event->thread)) { runningThreads[header->cpu] = thread; missingThreads--; } } // create the tool tip BString text; text << "Time: " << format_nanotime(time) << "\n"; text << "Thread: " << thread->Name() << " (" << thread->ID() << ")\n"; text << "State: " << thread_state_name(threadState); if (threadWaitObject != NULL) { char objectName[32]; snprintf(objectName, sizeof(objectName), "%#lx", threadWaitObject->Object()); text << " at " << wait_object_type_name(threadWaitObject->Type()) << " \"" << threadWaitObject->Name() << "\" " << objectName << "\n"; } else text << "\n"; text << "For: " << format_nanotime(time - threadStateTime) << " of " << format_nanotime(threadStateEndTime - threadStateTime); if (!ioRequests.IsEmpty()) { int32 scheduler = 0; for (int32 i = 0; Model::IORequest* request = ioRequests.ItemAt(i); i++) { if (i == 0 || scheduler != request->Scheduler()) { scheduler = request->Scheduler(); text << "\nI/O channel " << scheduler << ":"; } text << "\n " << (request->IsWrite() ? "write" : "read") << " request: " << request->BytesTransferred() << "/" << request->Length() << " "; if (request->IsFinished()) { text << (request->Status() == B_OK ? "ok" : "failed"); text << ", " << format_nanotime( request->FinishedTime() - request->ScheduledTime()); } else text << "unfinished"; for (size_t k = 0; k < request->operationCount; k++) { Model::IOOperation& operation = request->operations[k]; if (operation.startedEvent->time > fModel->BaseTime() + time || (operation.IsFinished() && operation.FinishedTime() <= fModel->BaseTime() + time)) { continue; } text << "\n " << (operation.IsWrite() ? "write" : "read") << ": " << operation.BytesTransferred() << "/" << operation.Length() << " "; if (operation.IsFinished()) { text << (request->Status() == B_OK ? "ok" : "failed"); text << ", " << format_nanotime(operation.FinishedTime() - operation.StartedTime()); } else text << "unfinished"; } } } text << "\n\n"; text << "Running threads:"; for (int32 i = 0; i < cpuCount; i++) { text << "\n " << i << ": "; if (Model::Thread* thread = runningThreads[i]) text << thread->Name() << " (" << thread->ID() << ")"; else text << "?"; } *_tip = new(std::nothrow) BTextToolTip(text); return *_tip != NULL; } virtual void Draw(BRect updateRect) { if (fModel == NULL || fSchedulingData.InitCheck() != B_OK) return; _UpdateData(); // draw the events // get the lines intersecting with the update rect int32 minLine, maxLine; GetLineRange(updateRect, minLine, maxLine); for (int32 i = minLine; i <= maxLine; i++) { // draw the background const rgb_color* activityColors; if (fSelectionModel->IsItemSelected(i)) { activityColors = fActivitySelectedColors; SetLowColor(fSelectedBackgroundColor); } else { activityColors = fActivityColors; SetLowColor(fBackgroundColor); } BRect lineRect = LineRect(i); FillRect(lineRect, B_SOLID_LOW); Model::Thread* thread = fModel->ThreadAt( fFilterModel->SelectedItemAt(i)); if (thread == NULL) continue; BRect schedulingLineRect = lineRect; schedulingLineRect.bottom -= lineRect.IntegerHeight() / 2; BRect ioLineRect = lineRect; ioLineRect.top = schedulingLineRect.bottom + 1; const Array& events = fSchedulingData.EventsForThread(thread->Index()); int32 eventCount = events.Size(); //printf("drawing events for thread %ld: %ld events\n", thread->Index(), eventCount); for (int32 k = 0; k < eventCount; k++) { const SchedulingEvent& event = events[k]; nanotime_t startTime = std::max(event.time, fStartTime); nanotime_t endTime = k + 1 < eventCount ? std::min(events[k + 1].time, fEndTime) : fEndTime; switch (event.state) { case RUNNING: case STILL_RUNNING: SetHighColor(activityColors[COLOR_RUNNING]); break; case PREEMPTED: SetHighColor(activityColors[COLOR_PREEMPTED]); break; case READY: SetHighColor(activityColors[COLOR_READY]); break; case WAITING: case UNKNOWN: default: continue; } BRect rect = schedulingLineRect; rect.left = startTime / fNSecsPerPixel; rect.right = endTime / fNSecsPerPixel - 1; FillRect(rect); } const Array& ioEvents = fSchedulingData.IOEventsForThread(thread->Index()); eventCount = ioEvents.Size(); for (int32 k = 0; k < eventCount; k++) { const IOSchedulingEvent& event = ioEvents[k]; nanotime_t startTime = std::max(event.time, fStartTime); nanotime_t endTime = k + 1 < eventCount ? std::min(ioEvents[k + 1].time, fEndTime) : fEndTime; switch (event.state) { case IO_SCHEDULING_STATE_PENDING_REQUEST: SetHighColor(activityColors[COLOR_IO_REQUEST]); break; case IO_SCHEDULING_STATE_PENDING_OPERATION: SetHighColor(activityColors[COLOR_IO_OPERATION]); break; case IO_SCHEDULING_STATE_IDLE: default: continue; } BRect rect = ioLineRect; rect.left = startTime / fNSecsPerPixel; rect.right = endTime / fNSecsPerPixel - 1; FillRect(rect); } } } private: // shorthands for the longish structure names typedef system_profiler_thread_enqueued_in_run_queue thread_enqueued_in_run_queue; typedef system_profiler_thread_removed_from_run_queue thread_removed_from_run_queue; private: void _UpdateData() { // get the interesting event time range nanotime_t startTime; nanotime_t endTime; _GetEventTimeRange(startTime, endTime); if (startTime == fStartTime && endTime == fEndTime) return; fStartTime = startTime; fEndTime = endTime; fSchedulingData.Clear(); //printf("MainWindow::SchedulingPage::SchedulingView::_UpdateData()\n"); //printf(" time range: %lld - %lld\n", startTime, endTime); // get a scheduling state close to our start time const Model::CompactSchedulingState* compactState = fModel->ClosestSchedulingState(startTime); //printf(" compactState: %p\n", compactState); fState.Clear(); status_t error = fState.Init(compactState); if (error != B_OK) return; // init the event stream BDebugEventInputStream input; error = input.SetTo((uint8*)fModel->EventData(), fModel->EventDataSize(), false); if (error == B_OK && compactState != NULL) error = input.Seek(compactState->EventOffset()); //printf(" event offset: %lld, input init error: %s\n", compactState != NULL ? compactState->EventOffset() : 0, strerror(error)); if (error != B_OK) return; fSchedulingData.SetRecordingEnabled( fState.LastEventTime() >= startTime); // add the initial thread states to the scheduling data if (compactState != NULL) { int32 threadStateCount = compactState->CountThreadsStates(); for (int32 i = 0; i < threadStateCount; i++) { const Model::CompactThreadSchedulingState* threadState = compactState->ThreadStateAt(i); switch (threadState->state) { case RUNNING: case STILL_RUNNING: fSchedulingData.AddRun(threadState->thread, threadState->lastTime); break; case PREEMPTED: fSchedulingData.AddPreemption(threadState->thread, threadState->lastTime); break; case READY: fSchedulingData.AddLatency(threadState->thread, threadState->lastTime); break; case WAITING: { Model::ThreadWaitObjectGroup* group = NULL; if (threadState->waitObject != NULL) { group = fModel->ThreadWaitObjectGroupFor( threadState->ID(), threadState->waitObject->Type(), threadState->waitObject->Object()); } fSchedulingData.AddWait(threadState->thread, threadState->lastTime, group); break; } case UNKNOWN: default: break; } } } // process the events while (true) { // get next event uint32 event; uint32 cpu; const void* buffer; off_t offset; ssize_t bufferSize = input.ReadNextEvent(&event, &cpu, &buffer, &offset); if (bufferSize < 0) { printf("failed to read event!\n"); return; } if (buffer == NULL) break; // process the event error = _ProcessEvent(event, cpu, buffer, bufferSize); if (error != B_OK) return; if (fState.LastEventTime() >= startTime) fSchedulingData.SetRecordingEnabled(true); if (fState.LastEventTime() >= endTime) break; } // process each thread's I/O events nanotime_t lastEventTime = fModel->BaseTime() - fModel->LastEventTime(); int32 threadCount = fModel->CountThreads(); for (int32 i = 0; i < threadCount; i++) { Model::Thread* thread = fModel->ThreadAt(i); Model::IORequest** requests = thread->IORequests(); size_t requestCount = thread->CountIORequests(); if (requestCount == 0) continue; // first request nanotime_t clusterStart = requests[0]->scheduledEvent->time; nanotime_t clusterEnd = requests[0]->finishedEvent != NULL ? requests[0]->finishedEvent->time : lastEventTime; BObjectList operations; // add first request operations for (size_t l = 0; l < requests[0]->operationCount; l++) operations.AddItem(&requests[0]->operations[l]); for (size_t k = 1; k < requestCount; k++) { nanotime_t requestStart = requests[k]->scheduledEvent->time; nanotime_t requestEnd = requests[k]->finishedEvent != NULL ? requests[k]->finishedEvent->time : lastEventTime; if (requestStart >= endTime) break; if (requestStart > clusterEnd) { if (clusterEnd > startTime) { _AddThreadIOData(thread, clusterStart, clusterEnd, operations); } operations.MakeEmpty(); clusterStart = requestStart; clusterEnd = requestEnd; } else clusterEnd = std::max(clusterEnd, requestEnd); // add request operations for (size_t l = 0; l < requests[k]->operationCount; l++) operations.AddItem(&requests[k]->operations[l]); } if (clusterEnd > startTime) _AddThreadIOData(thread, clusterStart, clusterEnd, operations); } } void _AddThreadIOData(Model::Thread* thread, nanotime_t startTime, nanotime_t endTime, BObjectList& operations) { //printf(" IORequest cluster: %lld - %lld\n", startTime, endTime); // start in pending request state fSchedulingData.AddIOState(thread, startTime - fModel->BaseTime(), IO_SCHEDULING_STATE_PENDING_REQUEST); int32 operationCount = operations.CountItems(); if (operationCount == 0) return; // sort the operations if (operationCount > 1) operations.SortItems(Model::IOOperation::CompareByTime); nanotime_t lastEventTime = fModel->BaseTime() + fModel->LastEventTime(); // process the operations Model::IOOperation* operation = operations.ItemAt(0); nanotime_t clusterStart = operation->startedEvent->time; nanotime_t clusterEnd = operation->finishedEvent != NULL ? operation->finishedEvent->time : lastEventTime; for (int32 i = 1; i < operationCount; i++) { operation = operations.ItemAt(i); nanotime_t operationStart = operation->startedEvent->time; nanotime_t operationEnd = operation->finishedEvent != NULL ? operation->finishedEvent->time : lastEventTime; if (operationStart > clusterEnd) { fSchedulingData.AddIOState(thread, clusterStart - fModel->BaseTime(), IO_SCHEDULING_STATE_PENDING_OPERATION); fSchedulingData.AddIOState(thread, clusterEnd - fModel->BaseTime(), IO_SCHEDULING_STATE_PENDING_REQUEST); clusterStart = operationStart; clusterEnd = operationEnd; } else clusterEnd = std::max(clusterEnd, operationEnd); } // add the last cluster fSchedulingData.AddIOState(thread, clusterStart - fModel->BaseTime(), IO_SCHEDULING_STATE_PENDING_OPERATION); fSchedulingData.AddIOState(thread, clusterEnd - fModel->BaseTime(), IO_SCHEDULING_STATE_PENDING_REQUEST); // after the end of the request cluster, state is idle again fSchedulingData.AddIOState(thread, endTime - fModel->BaseTime(), IO_SCHEDULING_STATE_IDLE); } void _GetEventTimeRange(nanotime_t& _startTime, nanotime_t& _endTime) { if (fModel != NULL) { float scrollOffset = _ScrollOffset(); _startTime = (nanotime_t)scrollOffset * fNSecsPerPixel; _endTime = (nanotime_t)(scrollOffset + Bounds().Width() + 1) * fNSecsPerPixel; } else { _startTime = 0; _endTime = 1; } } void _GetThreadAndTimeAt(BPoint point, Model::Thread*& _thread, nanotime_t& _time) { _thread = fModel->ThreadAt( fFilterModel->SelectedItemAt(LineAt(point))); _time = (nanotime_t)point.x * fNSecsPerPixel; } void _GetIORequests(Model::Thread* thread, nanotime_t time, BObjectList& ioRequests) { // find the time in the event data const Array& events = fSchedulingData.IOEventsForThread(thread->Index()); int32 eventCount = events.Size(); int32 lower = 0; int32 upper = eventCount - 1; while (lower < upper) { int32 mid = (lower + upper + 1) / 2; const IOSchedulingEvent& event = events[mid]; if (event.time > time) upper = mid - 1; else lower = mid; } if (lower < 0 || lower >= eventCount) return; if (events[lower].state == IO_SCHEDULING_STATE_IDLE) return; // find the beginning and end of the I/O request cluster while (lower > 0) { if (events[lower - 1].state == IO_SCHEDULING_STATE_IDLE) break; lower--; } while (upper + 1 < eventCount) { upper++; if (events[upper].state == IO_SCHEDULING_STATE_IDLE) break; } nanotime_t startTime = events[lower].time; nanotime_t endTime = events[upper].state == IO_SCHEDULING_STATE_IDLE ? events[upper].time : fEndTime; // convert to absolute time -- as used by the I/O requests startTime += fModel->BaseTime(); endTime += fModel->BaseTime(); time += fModel->BaseTime(); // collect the requests in the range Model::IORequest** requests = thread->IORequests(); size_t requestCount = thread->CountIORequests(); size_t index = thread->ClosestRequestStartIndex(startTime); for (; index < requestCount; index++) { Model::IORequest* request = requests[index]; if (request->ScheduledTime() >= endTime) break; if (request->ScheduledTime() > time || (request->finishedEvent != NULL && request->finishedEvent->time <= time)) { continue; } ioRequests.AddItem(request); } if (ioRequests.CountItems() > 1) ioRequests.SortItems(Model::IORequest::CompareSchedulerTime); } float _ScrollOffset() const { if (BScrollBar* scrollBar = ScrollBar(B_HORIZONTAL)) return scrollBar->Value(); return 0; } void _Zoom(float x, float steps) { if (steps == 0 || fModel == NULL) return; // compute the domain point where to zoom in float scrollOffset = _ScrollOffset(); double timeForX = (scrollOffset + x) * fNSecsPerPixel; uint32 factor = 4; if (steps < 0) { steps = -steps; factor = 1; } uint32 oldNsecPerPixel = fNSecsPerPixel; for (; steps > 0; steps--) fNSecsPerPixel = fNSecsPerPixel * factor / 2; if (fNSecsPerPixel < 1) fNSecsPerPixel = 1; else if (fNSecsPerPixel > 1000000) fNSecsPerPixel = 1000000; if (fNSecsPerPixel == oldNsecPerPixel) return; Invalidate(); UpdateScrollBar(); if (BScrollBar* scrollBar = ScrollBar(B_HORIZONTAL)) scrollBar->SetValue(timeForX / fNSecsPerPixel - x); if (fListener != NULL) { fListener->DataWidthChanged(); fListener->DataRangeChanged(); } } inline void _UpdateLastEventTime(nanotime_t time) { fState.SetLastEventTime(time - fModel->BaseTime()); } status_t _ProcessEvent(uint32 event, uint32 cpu, const void* buffer, size_t size) { switch (event) { case B_SYSTEM_PROFILER_THREAD_ADDED: _HandleThreadAdded((system_profiler_thread_added*)buffer); break; case B_SYSTEM_PROFILER_THREAD_REMOVED: _HandleThreadRemoved((system_profiler_thread_removed*)buffer); break; case B_SYSTEM_PROFILER_THREAD_SCHEDULED: _HandleThreadScheduled( (system_profiler_thread_scheduled*)buffer); break; case B_SYSTEM_PROFILER_THREAD_ENQUEUED_IN_RUN_QUEUE: _HandleThreadEnqueuedInRunQueue( (thread_enqueued_in_run_queue*)buffer); break; case B_SYSTEM_PROFILER_THREAD_REMOVED_FROM_RUN_QUEUE: _HandleThreadRemovedFromRunQueue( (thread_removed_from_run_queue*)buffer); break; default: break; } return B_OK; } void _HandleThreadAdded(system_profiler_thread_added* event) { //printf(" thread added: %ld\n", event->thread); // do we know the thread already? Model::ThreadSchedulingState* info = fState.LookupThread(event->thread); if (info != NULL) { // TODO: ? return; } Model::Thread* thread = fModel->ThreadByID(event->thread); if (thread == NULL) return; // create and add a ThreadSchedulingState info = new(std::nothrow) Model::ThreadSchedulingState(thread); if (info == NULL) return; fState.InsertThread(info); } void _HandleThreadRemoved(system_profiler_thread_removed* event) { //printf(" thread removed: %ld\n", event->thread); // Model::ThreadSchedulingState* thread = fState.LookupThread( // event->thread); // if (thread != NULL) { // fState.RemoveThread(thread); // delete thread; //// TODO: The thread will be unscheduled in a moment and cause a warning! So //// maybe keep it around in a separate hash table a bit longer? // } } void _HandleThreadScheduled(system_profiler_thread_scheduled* event) { _UpdateLastEventTime(event->time); Model::ThreadSchedulingState* thread = fState.LookupThread( event->thread); if (thread == NULL) { printf("Schedule event for unknown thread: %" B_PRId32 "\n", event->thread); return; } thread->lastTime = fState.LastEventTime(); thread->state = RUNNING; fSchedulingData.AddRun(thread->thread, fState.LastEventTime()); // unscheduled thread if (event->thread == event->previous_thread) return; thread = fState.LookupThread(event->previous_thread); if (thread == NULL) { printf("Schedule event for unknown previous thread: %" B_PRId32 "\n", event->previous_thread); return; } if (thread->state == STILL_RUNNING) { // thread preempted fSchedulingData.AddPreemption(thread->thread, fState.LastEventTime()); thread->lastTime = fState.LastEventTime(); thread->state = PREEMPTED; } else if (thread->state == RUNNING) { // thread starts waiting (it hadn't been added to the run // queue before being unscheduled) if (event->previous_thread_state == B_THREAD_WAITING) { addr_t waitObject = event->previous_thread_wait_object; switch (event->previous_thread_wait_object_type) { case THREAD_BLOCK_TYPE_SNOOZE: case THREAD_BLOCK_TYPE_SIGNAL: waitObject = 0; break; case THREAD_BLOCK_TYPE_SEMAPHORE: case THREAD_BLOCK_TYPE_CONDITION_VARIABLE: case THREAD_BLOCK_TYPE_MUTEX: case THREAD_BLOCK_TYPE_RW_LOCK: case THREAD_BLOCK_TYPE_OTHER: case THREAD_BLOCK_TYPE_OTHER_OBJECT: default: break; } fSchedulingData.AddWait(thread->thread, fState.LastEventTime(), fModel->ThreadWaitObjectGroupFor(thread->ID(), event->previous_thread_wait_object_type, waitObject)); } else { fSchedulingData.AddUnspecifiedWait(thread->thread, fState.LastEventTime()); } thread->lastTime = fState.LastEventTime(); thread->state = WAITING; } else if (thread->state == UNKNOWN) { uint32 threadState = event->previous_thread_state; if (threadState == B_THREAD_WAITING || threadState == B_THREAD_SUSPENDED) { fSchedulingData.AddWait(thread->thread, fState.LastEventTime(), NULL); thread->lastTime = fState.LastEventTime(); thread->state = WAITING; } else if (threadState == B_THREAD_READY) { thread->lastTime = fState.LastEventTime(); thread->state = PREEMPTED; fSchedulingData.AddPreemption(thread->thread, fState.LastEventTime()); } } } void _HandleThreadEnqueuedInRunQueue(thread_enqueued_in_run_queue* event) { _UpdateLastEventTime(event->time); Model::ThreadSchedulingState* thread = fState.LookupThread( event->thread); if (thread == NULL) { printf("Enqueued in run queue event for unknown thread: %" B_PRId32 "\n", event->thread); return; } if (thread->state == RUNNING || thread->state == STILL_RUNNING) { // Thread was running and is reentered into the run queue. This // is done by the scheduler, if the thread remains ready. thread->state = STILL_RUNNING; } else { // Thread was waiting and is ready now. nanotime_t diffTime = fState.LastEventTime() - thread->lastTime; if (thread->waitObject != NULL) { thread->waitObject->AddWait(diffTime); thread->waitObject = NULL; } fSchedulingData.AddLatency(thread->thread, fState.LastEventTime()); thread->lastTime = fState.LastEventTime(); thread->state = READY; } } void _HandleThreadRemovedFromRunQueue(thread_removed_from_run_queue* event) { _UpdateLastEventTime(event->time); Model::ThreadSchedulingState* thread = fState.LookupThread( event->thread); if (thread == NULL) { printf("Removed from run queue event for unknown thread: %" B_PRId32 "\n", event->thread); return; } // This really only happens when the thread priority is changed // while the thread is ready. fSchedulingData.AddUnspecifiedWait(thread->thread, fState.LastEventTime()); thread->lastTime = fState.LastEventTime(); thread->state = WAITING; } private: Model::SchedulingState fState; SchedulingData fSchedulingData; nanotime_t fStartTime; nanotime_t fEndTime; uint32 fNSecsPerPixel; BPoint fLastMousePos; Listener* fListener; rgb_color fActivityColors[ACTIVITY_COLOR_COUNT]; rgb_color fActivitySelectedColors[ACTIVITY_COLOR_COUNT]; }; class MainWindow::SchedulingPage::ViewPort : public BaseView, private HeaderListener, private SchedulingView::Listener { public: ViewPort(HeaderView* headerView, ThreadsView* threadsView, SchedulingView* schedulingView, FontInfo& fontInfo, ListSelectionModel* filterModel) : BaseView("viewport", fontInfo, filterModel), fHeaderView(headerView), fThreadsView(threadsView), fSchedulingView(schedulingView) { AddChild(threadsView); AddChild(schedulingView); fSchedulingView->SetListener(this); HeaderModel* headerModel = fHeaderView->Model(); Header* header = new Header((int32)fThreadsView->PreferredSize().width, (int32)fThreadsView->MinSize().width, (int32)fThreadsView->MaxSize().width, (int32)fThreadsView->PreferredSize().width, 0); header->SetValue("Thread"); headerModel->AddHeader(header); header->AddListener(this); header = new Header(100, 100, 10000, 200, 1); // TODO: Set header width correctly! header->SetValue("Activity"); header->SetHeaderRenderer(new TimelineHeaderRenderer); headerModel->AddHeader(header); // header->AddListener(this); } ~ViewPort() { } void RemoveListeners() { fHeaderView->Model()->HeaderAt(0)->RemoveListener(this); } void UpdateScrollBars() { float height = Frame().Height(); float dataHeight = std::max(height, fSchedulingView->MinSize().height); fSchedulingView->UpdateScrollBar(); if (BScrollBar* scrollBar = ScrollBar(B_VERTICAL)) { float range = dataHeight - height; if (range > 0) { scrollBar->SetRange(0, range); scrollBar->SetProportion( (height + 1) / (dataHeight + 1)); scrollBar->SetSteps(fFontInfo.lineHeight, height + 1); } else { scrollBar->SetRange(0, 0); scrollBar->SetProportion(1); } } } virtual BSize MinSize() { return BSize(10, 10); } virtual BSize MaxSize() { return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); } BSize PreferredSize() { BSize threadsViewSize(fThreadsView->PreferredSize()); BSize schedulingViewSize(fSchedulingView->PreferredSize()); return BSize(BLayoutUtils::AddDistances( threadsViewSize.width + kViewSeparationMargin, schedulingViewSize.width), std::max(threadsViewSize.height, schedulingViewSize.height)); } void DoLayout() { float width = Bounds().Width(); float height = fThreadsView->MinSize().Height(); float threadsViewWidth = 0; if (fHeaderView->Model()->HeaderAt(0) != NULL) threadsViewWidth = fHeaderView->HeaderFrame(0).Width(); threadsViewWidth = std::max(threadsViewWidth, fThreadsView->MinSize().width); threadsViewWidth = std::min(threadsViewWidth, fThreadsView->MaxSize().width); float schedulingViewLeft = threadsViewWidth + 1 + kViewSeparationMargin; float schedulingViewWidth = width - schedulingViewLeft; fThreadsView->MoveTo(0, 0); fThreadsView->ResizeTo(threadsViewWidth, height); fSchedulingView->MoveTo(schedulingViewLeft, 0); fSchedulingView->ResizeTo(schedulingViewWidth, height); if (Header* header = fHeaderView->Model()->HeaderAt(1)) { float headerWidth = schedulingViewWidth + 1 + kViewSeparationMargin; header->SetMinWidth(headerWidth); header->SetMaxWidth(headerWidth); header->SetPreferredWidth(headerWidth); header->SetWidth(headerWidth); } UpdateScrollBars(); } private: virtual void HeaderWidthChanged(Header* header) { if (header->ModelIndex() == 0) InvalidateLayout(); } virtual void DataWidthChanged() { } virtual void DataRangeChanged() { Header* header = fHeaderView->Model()->HeaderAt(1); if (header == NULL) return; nanotime_t startTime; nanotime_t endTime; fSchedulingView->GetDataRange(startTime, endTime); TimeRange* range = new(std::nothrow) TimeRange(startTime, endTime); if (range != NULL) { header->SetValue(BVariant(range, 'time')); range->ReleaseReference(); } } private: HeaderView* fHeaderView; ThreadsView* fThreadsView; SchedulingView* fSchedulingView; }; MainWindow::SchedulingPage::SchedulingPage(MainWindow* parent) : BGroupView(B_VERTICAL), fParent(parent), fModel(NULL), fScrollView(NULL), fViewPort(NULL), fThreadsView(NULL), fSchedulingView(NULL), fFilterModel(), fSelectionModel() { SetName("Scheduling"); be_plain_font->GetHeight(&fFontInfo.fontHeight); fFontInfo.lineHeight = ceilf(fFontInfo.fontHeight.ascent) + ceilf(fFontInfo.fontHeight.descent); HeaderView* headerView = new HeaderView; BView* scrollChild = BLayoutBuilder::Group<>(B_VERTICAL) .Add(headerView) .Add(fViewPort = new ViewPort(headerView, fThreadsView = new ThreadsView(fFontInfo, &fFilterModel, &fSelectionModel), fSchedulingView = new SchedulingView(fFontInfo, &fFilterModel, &fSelectionModel), fFontInfo, &fFilterModel)).View(); ; AddChild(fScrollView = new BScrollView("scroll", scrollChild, 0, true, true)); fScrollView->ScrollBar(B_HORIZONTAL)->SetTarget(fSchedulingView); fScrollView->ScrollBar(B_VERTICAL)->SetTarget(fViewPort); fViewPort->UpdateScrollBars(); } MainWindow::SchedulingPage::~SchedulingPage() { fViewPort->RemoveListeners(); fThreadsView->RemoveListeners(); fSchedulingView->RemoveListeners(); } void MainWindow::SchedulingPage::SetModel(Model* model) { if (model == fModel) return; fSelectionModel.Clear(); fFilterModel.Clear(); if (fModel != NULL) { } fModel = model; if (fModel != NULL) { fFilterModel.SelectItems(0, fModel->CountThreads(), false); } fViewPort->SetModel(fModel); fThreadsView->SetModel(fModel); fSchedulingView->SetModel(fModel); }