xref: /haiku/src/apps/debuganalyzer/gui/thread_window/ActivityPage.cpp (revision 579be822bbbbec746f58f1c3ed254dc14ca0c881)
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 
31 enum {
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 
40 static const rgb_color kRunTimeColor		= {0, 0, 0, 255};
41 static const rgb_color kWaitTimeColor		= {0, 255, 0, 255};
42 static const rgb_color kPreemptionTimeColor	= {0, 0, 255, 255};
43 static const rgb_color kLatencyTimeColor	= {255, 0, 0, 255};
44 
45 
46 class ThreadWindow::ActivityPage::ThreadActivityData
47 	: public ChartDataSource {
48 public:
ThreadActivityData(ThreadModel * model,int32 timeType)49 	ThreadActivityData(ThreadModel* model, int32 timeType)
50 		:
51 		fModel(model),
52 		fTimeType(timeType)
53 	{
54 	}
55 
Domain() const56 	virtual ChartDataRange Domain() const
57 	{
58 		return ChartDataRange(0.0, (double)fModel->GetModel()->LastEventTime());
59 	}
60 
Range() const61 	virtual ChartDataRange Range() const
62 	{
63 		return ChartDataRange(0.0, 1.0);
64 	}
65 
GetSamples(double start,double end,double * samples,int32 sampleCount)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 
323 private:
324 	ThreadModel*	fModel;
325 	int32			fTimeType;
326 };
327 
328 
ActivityPage()329 ThreadWindow::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 
~ActivityPage()396 ThreadWindow::ActivityPage::~ActivityPage()
397 {
398 	delete fRunTimeData;
399 	delete fWaitTimeData;
400 	delete fLatencyTimeData;
401 	delete fPreemptionTimeData;
402 	delete fActivityChartRenderer;
403 }
404 
405 
406 void
SetModel(ThreadModel * model)407 ThreadWindow::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 
435 void
MessageReceived(BMessage * message)436 ThreadWindow::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 
458 void
AttachedToWindow()459 ThreadWindow::ActivityPage::AttachedToWindow()
460 {
461 	fRunTimeCheckBox->SetTarget(this);
462 	fWaitTimeCheckBox->SetTarget(this);
463 	fPreemptionTimeCheckBox->SetTarget(this);
464 	fLatencyTimeCheckBox->SetTarget(this);
465 }
466 
467 
468 void
_UpdateChartDataEnabled(int timeType)469 ThreadWindow::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