⛏️ index : haiku.git

/*
 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include "thread_window/ActivityPage.h"

#include <new>

#include <GridLayoutBuilder.h>
#include <GroupLayoutBuilder.h>
#include <Message.h>
#include <ScrollView.h>

#include <AutoDeleter.h>

#include "ColorCheckBox.h"
#include "MessageCodes.h"
#include "ThreadModel.h"

#include "chart/NanotimeChartAxisLegendSource.h"
#include "chart/Chart.h"
#include "chart/ChartDataSource.h"
#include "chart/DefaultChartAxisLegendSource.h"
#include "chart/LegendChartAxis.h"
#include "chart/LineChartRenderer.h"
#include "chart/StringChartLegend.h"


enum {
	RUN_TIME			= 0,
	WAIT_TIME			= 1,
	PREEMPTION_TIME		= 2,
	LATENCY_TIME		= 3,
	UNSPECIFIED_TIME	= 4,
	TIME_TYPE_COUNT		= 5
};

static const rgb_color kRunTimeColor		= {0, 0, 0, 255};
static const rgb_color kWaitTimeColor		= {0, 255, 0, 255};
static const rgb_color kPreemptionTimeColor	= {0, 0, 255, 255};
static const rgb_color kLatencyTimeColor	= {255, 0, 0, 255};


class ThreadWindow::ActivityPage::ThreadActivityData
	: public ChartDataSource {
public:
	ThreadActivityData(ThreadModel* model, int32 timeType)
		:
		fModel(model),
		fTimeType(timeType)
	{
	}

	virtual ChartDataRange Domain() const
	{
		return ChartDataRange(0.0, (double)fModel->GetModel()->LastEventTime());
	}

	virtual ChartDataRange Range() const
	{
		return ChartDataRange(0.0, 1.0);
	}

	virtual void GetSamples(double start, double end, double* samples,
		int32 sampleCount)
	{
		thread_id threadID = fModel->GetThread()->ID();

		double sampleLength = (end - start) / (double)sampleCount;

		int32 startIndex = fModel->FindSchedulingEvent((nanotime_t)start);
		nanotime_t baseTime = fModel->GetModel()->BaseTime();

		enum ScheduleState {
			RUNNING,
			STILL_RUNNING,
			PREEMPTED,
			READY,
			WAITING,
			UNKNOWN
		};

		ScheduleState state = UNKNOWN;

		// get the first event and guess the initial state
		const system_profiler_event_header* header
			= fModel->SchedulingEventAt(startIndex);
		if (header == NULL) {
			for (int32 i = 0; i < sampleCount; i++)
				samples[i] = 0;
			return;
		}

		double previousEventTime = start;

		switch (header->event) {
			case B_SYSTEM_PROFILER_THREAD_SCHEDULED:
			{
				system_profiler_thread_scheduled* event
					= (system_profiler_thread_scheduled*)(header + 1);
				if (event->thread == threadID) {
					// thread scheduled -- it must have been ready before
					state = READY;
				} else {
					// thread unscheduled -- it was running earlier, but should
					// now be "still running" or "running", depending on whether
					// it had been added to the run queue before
					const system_profiler_event_header* previousHeader
						= fModel->SchedulingEventAt(startIndex - 1);
					if (previousHeader != NULL
						&& previousHeader->event
							== B_SYSTEM_PROFILER_THREAD_ENQUEUED_IN_RUN_QUEUE) {
						state = STILL_RUNNING;
					} else
						state = RUNNING;
				}

				previousEventTime = event->time;
				break;
			}

			case B_SYSTEM_PROFILER_THREAD_ENQUEUED_IN_RUN_QUEUE:
			{
				system_profiler_thread_enqueued_in_run_queue* event
					= (system_profiler_thread_enqueued_in_run_queue*)
						(header + 1);
				state = WAITING;
				previousEventTime = event->time;
				break;
			}

			case B_SYSTEM_PROFILER_THREAD_REMOVED_FROM_RUN_QUEUE:
			{
				system_profiler_thread_removed_from_run_queue* event
					= (system_profiler_thread_removed_from_run_queue*)
						(header + 1);
				state = READY;
				previousEventTime = event->time;
				break;
			}
		}

		double times[TIME_TYPE_COUNT] = { };
		int32 sampleIndex = -1;
		double nextSampleTime = start;

		for (int32 i = startIndex; ; i++) {
			header = fModel->SchedulingEventAt(i);
			double eventTime = previousEventTime;
			int32 timeType = -1;

			if (header != NULL) {
				switch (header->event) {
					case B_SYSTEM_PROFILER_THREAD_SCHEDULED:
					{
						system_profiler_thread_scheduled* event
							= (system_profiler_thread_scheduled*)(header + 1);
						eventTime = double(event->time - baseTime);

						if (event->thread == threadID) {
							// thread scheduled
							if (state == READY) {
								// thread scheduled after having been woken up
								timeType = LATENCY_TIME;
							} else if (state == PREEMPTED) {
								// thread scheduled after having been preempted
								// before
								timeType = PREEMPTION_TIME;
							} else if (state == STILL_RUNNING) {
								// Thread was running and continues to run.
								timeType = RUN_TIME;
							} else {
								// Can only happen, if we're missing context.
								// Impossible to guess what the thread was doing
								// before.
								timeType = UNSPECIFIED_TIME;
							}

							state = RUNNING;
						} else {
							// thread unscheduled
							if (state == STILL_RUNNING) {
								// thread preempted
								state = PREEMPTED;
							} else if (state == RUNNING) {
								// thread starts waiting (it hadn't been added
								// to the run queue before being unscheduled)
								state = WAITING;
							} else {
								// Can only happen, if we're missing context.
								// Obviously the thread was running, but we
								// can't guess the new thread state.
							}

							timeType = RUN_TIME;
						}

						break;
					}

					case B_SYSTEM_PROFILER_THREAD_ENQUEUED_IN_RUN_QUEUE:
					{
						system_profiler_thread_enqueued_in_run_queue* event
							= (system_profiler_thread_enqueued_in_run_queue*)
								(header + 1);
						eventTime = double(event->time - baseTime);

						if (state == RUNNING || state == STILL_RUNNING) {
							// Thread was running and is reentered into the run
							// queue. This is done by the scheduler, if the
							// thread remains ready.
							state = STILL_RUNNING;
							timeType = RUN_TIME;
						} else if (state == READY || state == PREEMPTED) {
							// Happens only after having been removed from the
							// run queue for altering the priority.
							timeType = state == READY
								? LATENCY_TIME : PREEMPTION_TIME;
						} else {
							// Thread was waiting and is ready now.
							state = READY;
							timeType = WAIT_TIME;
						}

						break;
					}

					case B_SYSTEM_PROFILER_THREAD_REMOVED_FROM_RUN_QUEUE:
					{
						system_profiler_thread_removed_from_run_queue* event
							= (system_profiler_thread_removed_from_run_queue*)
								(header + 1);
						eventTime = double(event->time - baseTime);

						// This really only happens when the thread priority is
						// changed while the thread is ready.

						if (state == RUNNING) {
							// This should never happen.
							state = READY;
							timeType = RUN_TIME;
						} else if (state == READY) {
							// Thread was ready after having been woken up.
							timeType = LATENCY_TIME;
						} else if (state == PREEMPTED) {
							// Thread was ready after having been preempted.
							timeType = PREEMPTION_TIME;
						} else
							state = READY;

						break;
					}
				}
			} else {
				// no more events for this thread -- assume things go on like
				// this until the death of the thread
				switch (state) {
					case RUNNING:
					case STILL_RUNNING:
						timeType = RUN_TIME;
						break;
					case PREEMPTED:
						timeType = PREEMPTION_TIME;
						break;
					case READY:
						timeType = LATENCY_TIME;
						break;
					case WAITING:
						timeType = WAIT_TIME;
						break;
					case UNKNOWN:
						timeType = UNSPECIFIED_TIME;
						break;
				}

				if (fModel->GetThread()->DeletionTime() >= 0) {
					eventTime = double(fModel->GetThread()->DeletionTime()
						- baseTime);
					if (eventTime <= previousEventTime) {
						// thread is dead
						eventTime = end + 1;
						timeType = UNSPECIFIED_TIME;
					}
				} else
					eventTime = end + 1;
			}

			if (timeType < 0)
				continue;

			while (eventTime >= nextSampleTime) {
				// We've reached the next sample. Partially add this time to the
				// current sample.
				times[timeType] += nextSampleTime - previousEventTime;
				previousEventTime = nextSampleTime;

				// write the sample value
				if (sampleIndex >= 0) {
					samples[sampleIndex] = times[fTimeType] / sampleLength;
					if (samples[sampleIndex] > 1.0)
						samples[sampleIndex] = 1.0;
				}

				for (int32 k = 0; k < TIME_TYPE_COUNT; k++)
					times[k] = 0;

				// compute the time of the next sample
				if (++sampleIndex >= sampleCount)
					return;
				nextSampleTime = start
					+ (end - start)
						* (double)(sampleIndex + 1) / (double)sampleCount;
			}

			// next sample not yet reached -- just add the time
			times[timeType] += double(eventTime - previousEventTime);
			previousEventTime = eventTime;
		}
	}

private:
	ThreadModel*	fModel;
	int32			fTimeType;
};


ThreadWindow::ActivityPage::ActivityPage()
	:
	BGroupView(B_VERTICAL),
	fThreadModel(NULL),
	fActivityChart(NULL),
	fActivityChartRenderer(NULL),
	fRunTimeData(NULL),
	fWaitTimeData(NULL),
	fPreemptionTimeData(NULL),
	fLatencyTimeData(NULL)
{
	SetName("Activity");

	GroupLayout()->SetInsets(10, 10, 10, 10);

	fActivityChartRenderer = new LineChartRenderer;
	ObjectDeleter<ChartRenderer> rendererDeleter(fActivityChartRenderer);

	fActivityChart = new Chart(fActivityChartRenderer);
	fActivityChart->SetDomainZoomLimit(1000);
		// maximal zoom: 1 ms for the whole displayed domain

	BGroupLayoutBuilder(this)
		.Add(new BScrollView("activity scroll", fActivityChart, 0, true, false))
		.AddStrut(20)
		.Add(BGridLayoutBuilder()
			.Add(fRunTimeCheckBox = new ColorCheckBox("Run time", kRunTimeColor,
					new BMessage(MSG_CHECK_BOX_RUN_TIME)),
				0, 0)
			.Add(fWaitTimeCheckBox = new ColorCheckBox("Wait time",
					kWaitTimeColor, new BMessage(MSG_CHECK_BOX_WAIT_TIME)),
				1, 0)
			.Add(fPreemptionTimeCheckBox = new ColorCheckBox("Preemption time",
					kPreemptionTimeColor,
					new BMessage(MSG_CHECK_BOX_PREEMPTION_TIME)),
				0, 1)
			.Add(fLatencyTimeCheckBox = new ColorCheckBox("Latency time",
					kLatencyTimeColor,
					new BMessage(MSG_CHECK_BOX_LATENCY_TIME)),
				1, 1)
		)
	;

	rendererDeleter.Detach();

	// enable the run time chart data
	fRunTimeCheckBox->CheckBox()->SetValue(B_CONTROL_ON);

// TODO: Allocation management...
	LegendChartAxis* axis = new LegendChartAxis(
		new NanotimeChartAxisLegendSource, new StringChartLegendRenderer);
	fActivityChart->SetAxis(CHART_AXIS_BOTTOM, axis);

	axis = new LegendChartAxis(
		new NanotimeChartAxisLegendSource, new StringChartLegendRenderer);
	fActivityChart->SetAxis(CHART_AXIS_TOP, axis);

	axis = new LegendChartAxis(
		new DefaultChartAxisLegendSource, new StringChartLegendRenderer);
	fActivityChart->SetAxis(CHART_AXIS_LEFT, axis);

	axis = new LegendChartAxis(
		new DefaultChartAxisLegendSource, new StringChartLegendRenderer);
	fActivityChart->SetAxis(CHART_AXIS_RIGHT, axis);
}


ThreadWindow::ActivityPage::~ActivityPage()
{
	delete fRunTimeData;
	delete fWaitTimeData;
	delete fLatencyTimeData;
	delete fPreemptionTimeData;
	delete fActivityChartRenderer;
}


void
ThreadWindow::ActivityPage::SetModel(ThreadModel* model)
{
	if (model == fThreadModel)
		return;

	if (fThreadModel != NULL) {
		fActivityChart->RemoveAllDataSources();
		delete fRunTimeData;
		delete fWaitTimeData;
		delete fLatencyTimeData;
		delete fPreemptionTimeData;
		fRunTimeData = NULL;
		fWaitTimeData = NULL;
		fLatencyTimeData = NULL;
		fPreemptionTimeData = NULL;
	}

	fThreadModel = model;

	if (fThreadModel != NULL) {
		_UpdateChartDataEnabled(RUN_TIME);
		_UpdateChartDataEnabled(WAIT_TIME);
		_UpdateChartDataEnabled(PREEMPTION_TIME);
		_UpdateChartDataEnabled(LATENCY_TIME);
	}
}


void
ThreadWindow::ActivityPage::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case MSG_CHECK_BOX_RUN_TIME:
			_UpdateChartDataEnabled(RUN_TIME);
			break;
		case MSG_CHECK_BOX_WAIT_TIME:
			_UpdateChartDataEnabled(WAIT_TIME);
			break;
		case MSG_CHECK_BOX_PREEMPTION_TIME:
			_UpdateChartDataEnabled(PREEMPTION_TIME);
			break;
		case MSG_CHECK_BOX_LATENCY_TIME:
			_UpdateChartDataEnabled(LATENCY_TIME);
			break;
		default:
			BGroupView::MessageReceived(message);
			break;
	}
}


void
ThreadWindow::ActivityPage::AttachedToWindow()
{
	fRunTimeCheckBox->SetTarget(this);
	fWaitTimeCheckBox->SetTarget(this);
	fPreemptionTimeCheckBox->SetTarget(this);
	fLatencyTimeCheckBox->SetTarget(this);
}


void
ThreadWindow::ActivityPage::_UpdateChartDataEnabled(int timeType)
{
	ThreadActivityData** data;
	ColorCheckBox* checkBox;
	rgb_color color;

	switch (timeType) {
		case RUN_TIME:
			data = &fRunTimeData;
			checkBox = fRunTimeCheckBox;
			color = kRunTimeColor;
			break;
		case WAIT_TIME:
			data = &fWaitTimeData;
			checkBox = fWaitTimeCheckBox;
			color = kWaitTimeColor;
			break;
		case PREEMPTION_TIME:
			data = &fPreemptionTimeData;
			checkBox = fPreemptionTimeCheckBox;
			color = kPreemptionTimeColor;
			break;
		case LATENCY_TIME:
			data = &fLatencyTimeData;
			checkBox = fLatencyTimeCheckBox;
			color = kLatencyTimeColor;
			break;
		default:
			return;
	}

	bool enabled = checkBox->CheckBox()->Value() == B_CONTROL_ON;

	if ((*data != NULL) == enabled)
		return;

	if (enabled) {
		LineChartRendererDataSourceConfig config(color);

		*data = new(std::nothrow) ThreadActivityData(fThreadModel, timeType);
		if (*data == NULL)
			return;

		fActivityChart->AddDataSource(*data, &config);
	} else {
		fActivityChart->RemoveDataSource(*data);
		delete *data;
		*data = NULL;
	}
}