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: 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 323 private: 324 ThreadModel* fModel; 325 int32 fTimeType; 326 }; 327 328 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 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 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 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 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 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