1/*
2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "thread_window/ActivityPage.h"
8
9#include <new>
10
11#include <GridLayoutBuilder.h>
12#include <GroupLayoutBuilder.h>
13#include <Message.h>
14#include <ScrollView.h>
15
16#include <AutoDeleter.h>
17
18#include "ColorCheckBox.h"
19#include "MessageCodes.h"
20#include "ThreadModel.h"
21
22#include "chart/NanotimeChartAxisLegendSource.h"
23#include "chart/Chart.h"
24#include "chart/ChartDataSource.h"
25#include "chart/DefaultChartAxisLegendSource.h"
26#include "chart/LegendChartAxis.h"
27#include "chart/LineChartRenderer.h"
28#include "chart/StringChartLegend.h"
29
30
31enum {
32	RUN_TIME			= 0,
33	WAIT_TIME			= 1,
34	PREEMPTION_TIME		= 2,
35	LATENCY_TIME		= 3,
36	UNSPECIFIED_TIME	= 4,
37	TIME_TYPE_COUNT		= 5
38};
39
40static const rgb_color kRunTimeColor		= {0, 0, 0, 255};
41static const rgb_color kWaitTimeColor		= {0, 255, 0, 255};
42static const rgb_color kPreemptionTimeColor	= {0, 0, 255, 255};
43static const rgb_color kLatencyTimeColor	= {255, 0, 0, 255};
44
45
46class ThreadWindow::ActivityPage::ThreadActivityData
47	: public ChartDataSource {
48public:
49	ThreadActivityData(ThreadModel* model, int32 timeType)
50		:
51		fModel(model),
52		fTimeType(timeType)
53	{
54	}
55
56	virtual ChartDataRange Domain() const
57	{
58		return ChartDataRange(0.0, (double)fModel->GetModel()->LastEventTime());
59	}
60
61	virtual ChartDataRange Range() const
62	{
63		return ChartDataRange(0.0, 1.0);
64	}
65
66	virtual void GetSamples(double start, double end, double* samples,
67		int32 sampleCount)
68	{
69		thread_id threadID = fModel->GetThread()->ID();
70
71		double sampleLength = (end - start) / (double)sampleCount;
72
73		int32 startIndex = fModel->FindSchedulingEvent((nanotime_t)start);
74		nanotime_t baseTime = fModel->GetModel()->BaseTime();
75
76		enum ScheduleState {
77			RUNNING,
78			STILL_RUNNING,
79			PREEMPTED,
80			READY,
81			WAITING,
82			UNKNOWN
83		};
84
85		ScheduleState state = UNKNOWN;
86
87		// get the first event and guess the initial state
88		const system_profiler_event_header* header
89			= fModel->SchedulingEventAt(startIndex);
90		if (header == NULL) {
91			for (int32 i = 0; i < sampleCount; i++)
92				samples[i] = 0;
93			return;
94		}
95
96		double previousEventTime = start;
97
98		switch (header->event) {
99			case B_SYSTEM_PROFILER_THREAD_SCHEDULED:
100			{
101				system_profiler_thread_scheduled* event
102					= (system_profiler_thread_scheduled*)(header + 1);
103				if (event->thread == threadID) {
104					// thread scheduled -- it must have been ready before
105					state = READY;
106				} else {
107					// thread unscheduled -- it was running earlier, but should
108					// now be "still running" or "running", depending on whether
109					// it had been added to the run queue before
110					const system_profiler_event_header* previousHeader
111						= fModel->SchedulingEventAt(startIndex - 1);
112					if (previousHeader != NULL
113						&& previousHeader->event
114							== B_SYSTEM_PROFILER_THREAD_ENQUEUED_IN_RUN_QUEUE) {
115						state = STILL_RUNNING;
116					} else
117						state = RUNNING;
118				}
119
120				previousEventTime = event->time;
121				break;
122			}
123
124			case B_SYSTEM_PROFILER_THREAD_ENQUEUED_IN_RUN_QUEUE:
125			{
126				system_profiler_thread_enqueued_in_run_queue* event
127					= (system_profiler_thread_enqueued_in_run_queue*)
128						(header + 1);
129				state = WAITING;
130				previousEventTime = event->time;
131				break;
132			}
133
134			case B_SYSTEM_PROFILER_THREAD_REMOVED_FROM_RUN_QUEUE:
135			{
136				system_profiler_thread_removed_from_run_queue* event
137					= (system_profiler_thread_removed_from_run_queue*)
138						(header + 1);
139				state = READY;
140				previousEventTime = event->time;
141				break;
142			}
143		}
144
145		double times[TIME_TYPE_COUNT] = { };
146		int32 sampleIndex = -1;
147		double nextSampleTime = start;
148
149		for (int32 i = startIndex; ; i++) {
150			header = fModel->SchedulingEventAt(i);
151			double eventTime = previousEventTime;
152			int32 timeType = -1;
153
154			if (header != NULL) {
155				switch (header->event) {
156					case B_SYSTEM_PROFILER_THREAD_SCHEDULED:
157					{
158						system_profiler_thread_scheduled* event
159							= (system_profiler_thread_scheduled*)(header + 1);
160						eventTime = double(event->time - baseTime);
161
162						if (event->thread == threadID) {
163							// thread scheduled
164							if (state == READY) {
165								// thread scheduled after having been woken up
166								timeType = LATENCY_TIME;
167							} else if (state == PREEMPTED) {
168								// thread scheduled after having been preempted
169								// before
170								timeType = PREEMPTION_TIME;
171							} else if (state == STILL_RUNNING) {
172								// Thread was running and continues to run.
173								timeType = RUN_TIME;
174							} else {
175								// Can only happen, if we're missing context.
176								// Impossible to guess what the thread was doing
177								// before.
178								timeType = UNSPECIFIED_TIME;
179							}
180
181							state = RUNNING;
182						} else {
183							// thread unscheduled
184							if (state == STILL_RUNNING) {
185								// thread preempted
186								state = PREEMPTED;
187							} else if (state == RUNNING) {
188								// thread starts waiting (it hadn't been added
189								// to the run queue before being unscheduled)
190								state = WAITING;
191							} else {
192								// Can only happen, if we're missing context.
193								// Obviously the thread was running, but we
194								// can't guess the new thread state.
195							}
196
197							timeType = RUN_TIME;
198						}
199
200						break;
201					}
202
203					case B_SYSTEM_PROFILER_THREAD_ENQUEUED_IN_RUN_QUEUE:
204					{
205						system_profiler_thread_enqueued_in_run_queue* event
206							= (system_profiler_thread_enqueued_in_run_queue*)
207								(header + 1);
208						eventTime = double(event->time - baseTime);
209
210						if (state == RUNNING || state == STILL_RUNNING) {
211							// Thread was running and is reentered into the run
212							// queue. This is done by the scheduler, if the
213							// thread remains ready.
214							state = STILL_RUNNING;
215							timeType = RUN_TIME;
216						} else if (state == READY || state == PREEMPTED) {
217							// Happens only after having been removed from the
218							// run queue for altering the priority.
219							timeType = state == READY
220								? LATENCY_TIME : PREEMPTION_TIME;
221						} else {
222							// Thread was waiting and is ready now.
223							state = READY;
224							timeType = WAIT_TIME;
225						}
226
227						break;
228					}
229
230					case B_SYSTEM_PROFILER_THREAD_REMOVED_FROM_RUN_QUEUE:
231					{
232						system_profiler_thread_removed_from_run_queue* event
233							= (system_profiler_thread_removed_from_run_queue*)
234								(header + 1);
235						eventTime = double(event->time - baseTime);
236
237						// This really only happens when the thread priority is
238						// changed while the thread is ready.
239
240						if (state == RUNNING) {
241							// This should never happen.
242							state = READY;
243							timeType = RUN_TIME;
244						} else if (state == READY) {
245							// Thread was ready after having been woken up.
246							timeType = LATENCY_TIME;
247						} else if (state == PREEMPTED) {
248							// Thread was ready after having been preempted.
249							timeType = PREEMPTION_TIME;
250						} else
251							state = READY;
252
253						break;
254					}
255				}
256			} else {
257				// no more events for this thread -- assume things go on like
258				// this until the death of the thread
259				switch (state) {
260					case RUNNING:
261					case STILL_RUNNING:
262						timeType = RUN_TIME;
263						break;
264					case PREEMPTED:
265						timeType = PREEMPTION_TIME;
266						break;
267					case READY:
268						timeType = LATENCY_TIME;
269						break;
270					case WAITING:
271						timeType = WAIT_TIME;
272						break;
273					case UNKNOWN:
274						timeType = UNSPECIFIED_TIME;
275						break;
276				}
277
278				if (fModel->GetThread()->DeletionTime() >= 0) {
279					eventTime = double(fModel->GetThread()->DeletionTime()
280						- baseTime);
281					if (eventTime <= previousEventTime) {
282						// thread is dead
283						eventTime = end + 1;
284						timeType = UNSPECIFIED_TIME;
285					}
286				} else
287					eventTime = end + 1;
288			}
289
290			if (timeType < 0)
291				continue;
292
293			while (eventTime >= nextSampleTime) {
294				// We've reached the next sample. Partially add this time to the
295				// current sample.
296				times[timeType] += nextSampleTime - previousEventTime;
297				previousEventTime = nextSampleTime;
298
299				// write the sample value
300				if (sampleIndex >= 0) {
301					samples[sampleIndex] = times[fTimeType] / sampleLength;
302					if (samples[sampleIndex] > 1.0)
303						samples[sampleIndex] = 1.0;
304				}
305
306				for (int32 k = 0; k < TIME_TYPE_COUNT; k++)
307					times[k] = 0;
308
309				// compute the time of the next sample
310				if (++sampleIndex >= sampleCount)
311					return;
312				nextSampleTime = start
313					+ (end - start)
314						* (double)(sampleIndex + 1) / (double)sampleCount;
315			}
316
317			// next sample not yet reached -- just add the time
318			times[timeType] += double(eventTime - previousEventTime);
319			previousEventTime = eventTime;
320		}
321	}
322
323private:
324	ThreadModel*	fModel;
325	int32			fTimeType;
326};
327
328
329ThreadWindow::ActivityPage::ActivityPage()
330	:
331	BGroupView(B_VERTICAL),
332	fThreadModel(NULL),
333	fActivityChart(NULL),
334	fActivityChartRenderer(NULL),
335	fRunTimeData(NULL),
336	fWaitTimeData(NULL),
337	fPreemptionTimeData(NULL),
338	fLatencyTimeData(NULL)
339{
340	SetName("Activity");
341
342	GroupLayout()->SetInsets(10, 10, 10, 10);
343
344	fActivityChartRenderer = new LineChartRenderer;
345	ObjectDeleter<ChartRenderer> rendererDeleter(fActivityChartRenderer);
346
347	fActivityChart = new Chart(fActivityChartRenderer);
348	fActivityChart->SetDomainZoomLimit(1000);
349		// maximal zoom: 1 ms for the whole displayed domain
350
351	BGroupLayoutBuilder(this)
352		.Add(new BScrollView("activity scroll", fActivityChart, 0, true, false))
353		.AddStrut(20)
354		.Add(BGridLayoutBuilder()
355			.Add(fRunTimeCheckBox = new ColorCheckBox("Run time", kRunTimeColor,
356					new BMessage(MSG_CHECK_BOX_RUN_TIME)),
357				0, 0)
358			.Add(fWaitTimeCheckBox = new ColorCheckBox("Wait time",
359					kWaitTimeColor, new BMessage(MSG_CHECK_BOX_WAIT_TIME)),
360				1, 0)
361			.Add(fPreemptionTimeCheckBox = new ColorCheckBox("Preemption time",
362					kPreemptionTimeColor,
363					new BMessage(MSG_CHECK_BOX_PREEMPTION_TIME)),
364				0, 1)
365			.Add(fLatencyTimeCheckBox = new ColorCheckBox("Latency time",
366					kLatencyTimeColor,
367					new BMessage(MSG_CHECK_BOX_LATENCY_TIME)),
368				1, 1)
369		)
370	;
371
372	rendererDeleter.Detach();
373
374	// enable the run time chart data
375	fRunTimeCheckBox->CheckBox()->SetValue(B_CONTROL_ON);
376
377// TODO: Allocation management...
378	LegendChartAxis* axis = new LegendChartAxis(
379		new NanotimeChartAxisLegendSource, new StringChartLegendRenderer);
380	fActivityChart->SetAxis(CHART_AXIS_BOTTOM, axis);
381
382	axis = new LegendChartAxis(
383		new NanotimeChartAxisLegendSource, new StringChartLegendRenderer);
384	fActivityChart->SetAxis(CHART_AXIS_TOP, axis);
385
386	axis = new LegendChartAxis(
387		new DefaultChartAxisLegendSource, new StringChartLegendRenderer);
388	fActivityChart->SetAxis(CHART_AXIS_LEFT, axis);
389
390	axis = new LegendChartAxis(
391		new DefaultChartAxisLegendSource, new StringChartLegendRenderer);
392	fActivityChart->SetAxis(CHART_AXIS_RIGHT, axis);
393}
394
395
396ThreadWindow::ActivityPage::~ActivityPage()
397{
398	delete fRunTimeData;
399	delete fWaitTimeData;
400	delete fLatencyTimeData;
401	delete fPreemptionTimeData;
402	delete fActivityChartRenderer;
403}
404
405
406void
407ThreadWindow::ActivityPage::SetModel(ThreadModel* model)
408{
409	if (model == fThreadModel)
410		return;
411
412	if (fThreadModel != NULL) {
413		fActivityChart->RemoveAllDataSources();
414		delete fRunTimeData;
415		delete fWaitTimeData;
416		delete fLatencyTimeData;
417		delete fPreemptionTimeData;
418		fRunTimeData = NULL;
419		fWaitTimeData = NULL;
420		fLatencyTimeData = NULL;
421		fPreemptionTimeData = NULL;
422	}
423
424	fThreadModel = model;
425
426	if (fThreadModel != NULL) {
427		_UpdateChartDataEnabled(RUN_TIME);
428		_UpdateChartDataEnabled(WAIT_TIME);
429		_UpdateChartDataEnabled(PREEMPTION_TIME);
430		_UpdateChartDataEnabled(LATENCY_TIME);
431	}
432}
433
434
435void
436ThreadWindow::ActivityPage::MessageReceived(BMessage* message)
437{
438	switch (message->what) {
439		case MSG_CHECK_BOX_RUN_TIME:
440			_UpdateChartDataEnabled(RUN_TIME);
441			break;
442		case MSG_CHECK_BOX_WAIT_TIME:
443			_UpdateChartDataEnabled(WAIT_TIME);
444			break;
445		case MSG_CHECK_BOX_PREEMPTION_TIME:
446			_UpdateChartDataEnabled(PREEMPTION_TIME);
447			break;
448		case MSG_CHECK_BOX_LATENCY_TIME:
449			_UpdateChartDataEnabled(LATENCY_TIME);
450			break;
451		default:
452			BGroupView::MessageReceived(message);
453			break;
454	}
455}
456
457
458void
459ThreadWindow::ActivityPage::AttachedToWindow()
460{
461	fRunTimeCheckBox->SetTarget(this);
462	fWaitTimeCheckBox->SetTarget(this);
463	fPreemptionTimeCheckBox->SetTarget(this);
464	fLatencyTimeCheckBox->SetTarget(this);
465}
466
467
468void
469ThreadWindow::ActivityPage::_UpdateChartDataEnabled(int timeType)
470{
471	ThreadActivityData** data;
472	ColorCheckBox* checkBox;
473	rgb_color color;
474
475	switch (timeType) {
476		case RUN_TIME:
477			data = &fRunTimeData;
478			checkBox = fRunTimeCheckBox;
479			color = kRunTimeColor;
480			break;
481		case WAIT_TIME:
482			data = &fWaitTimeData;
483			checkBox = fWaitTimeCheckBox;
484			color = kWaitTimeColor;
485			break;
486		case PREEMPTION_TIME:
487			data = &fPreemptionTimeData;
488			checkBox = fPreemptionTimeCheckBox;
489			color = kPreemptionTimeColor;
490			break;
491		case LATENCY_TIME:
492			data = &fLatencyTimeData;
493			checkBox = fLatencyTimeCheckBox;
494			color = kLatencyTimeColor;
495			break;
496		default:
497			return;
498	}
499
500	bool enabled = checkBox->CheckBox()->Value() == B_CONTROL_ON;
501
502	if ((*data != NULL) == enabled)
503		return;
504
505	if (enabled) {
506		LineChartRendererDataSourceConfig config(color);
507
508		*data = new(std::nothrow) ThreadActivityData(fThreadModel, timeType);
509		if (*data == NULL)
510			return;
511
512		fActivityChart->AddDataSource(*data, &config);
513	} else {
514		fActivityChart->RemoveDataSource(*data);
515		delete *data;
516		*data = NULL;
517	}
518}
519