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