* 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;
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) {
state = READY;
} else {
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) {
if (state == READY) {
timeType = LATENCY_TIME;
} else if (state == PREEMPTED) {
timeType = PREEMPTION_TIME;
} else if (state == STILL_RUNNING) {
timeType = RUN_TIME;
} else {
timeType = UNSPECIFIED_TIME;
}
state = RUNNING;
} else {
if (state == STILL_RUNNING) {
state = PREEMPTED;
} else if (state == RUNNING) {
state = WAITING;
} else {
}
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) {
state = STILL_RUNNING;
timeType = RUN_TIME;
} else if (state == READY || state == PREEMPTED) {
timeType = state == READY
? LATENCY_TIME : PREEMPTION_TIME;
} else {
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);
if (state == RUNNING) {
state = READY;
timeType = RUN_TIME;
} else if (state == READY) {
timeType = LATENCY_TIME;
} else if (state == PREEMPTED) {
timeType = PREEMPTION_TIME;
} else
state = READY;
break;
}
}
} else {
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) {
eventTime = end + 1;
timeType = UNSPECIFIED_TIME;
}
} else
eventTime = end + 1;
}
if (timeType < 0)
continue;
while (eventTime >= nextSampleTime) {
times[timeType] += nextSampleTime - previousEventTime;
previousEventTime = nextSampleTime;
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;
if (++sampleIndex >= sampleCount)
return;
nextSampleTime = start
+ (end - start)
* (double)(sampleIndex + 1) / (double)sampleCount;
}
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);
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();
fRunTimeCheckBox->CheckBox()->SetValue(B_CONTROL_ON);
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;
}
}