Skip to content

Commit

Permalink
New Celery/Queries Execution Status API (#3057)
Browse files Browse the repository at this point in the history
* Remove QueryTaskTracker

* Remove scheudling of cleanup_tasks

* Add Celery introspection tools

* First iteration of updating the admin API.

* Show more details

* Add option to skip building npm in Dockerfile

* Show started_at

* update the refresh schedule, as it's too fast

* Update Celery monitor to report on all active tasks.

* Update task parsing for new format

* WIP: improved celery status screen

* Fix property name.

* Update counters

* Update tab name

* Update counters names

* Move component to its own file and fix lint issues

* Add migratin to remove Redis keys

* Improve columns layout

* Remove skip_npm_build arg as it's not used anymore.

* Convert query from SQL to Python

* Simplify column definition.

* Show alert on error.
  • Loading branch information
arikfr authored Mar 10, 2019
1 parent 12d2a04 commit 26f0ce0
Show file tree
Hide file tree
Showing 13 changed files with 404 additions and 341 deletions.
14 changes: 11 additions & 3 deletions client/app/assets/less/ant.less
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,15 @@
@import '~antd/lib/dropdown/style/index';
@import '~antd/lib/menu/style/index';
@import '~antd/lib/list/style/index';
@import "~antd/lib/badge/style/index";
@import "~antd/lib/card/style/index";
@import "~antd/lib/spin/style/index";
@import "~antd/lib/tabs/style/index";
@import 'inc/ant-variables';

// Remove bold in labels for Ant checkboxes and radio buttons
.ant-checkbox-wrapper, .ant-radio-wrapper {
.ant-checkbox-wrapper,
.ant-radio-wrapper {
font-weight: normal;
}

Expand Down Expand Up @@ -158,7 +163,9 @@
.@{table-prefix-cls} {
color: inherit;

tr, th, td {
tr,
th,
td {
transition: none !important;
}

Expand Down Expand Up @@ -213,7 +220,8 @@
// styling for short modals (no lines)
.@{dialog-prefix-cls}.shortModal {
.@{dialog-prefix-cls} {
&-header, &-footer {
&-header,
&-footer {
border: none;
padding: 16px;
}
Expand Down
219 changes: 219 additions & 0 deletions client/app/components/admin/CeleryStatus.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import React from 'react';
import PropTypes from 'prop-types';
import { $http } from '@/services/ng';
import Table from 'antd/lib/table';
import Col from 'antd/lib/col';
import Row from 'antd/lib/row';
import Card from 'antd/lib/card';
import Spin from 'antd/lib/spin';
import Badge from 'antd/lib/badge';
import Tabs from 'antd/lib/tabs';
import Alert from 'antd/lib/alert';
import moment from 'moment';
import values from 'lodash/values';
import { Columns } from '@/components/items-list/components/ItemsTable';

function parseTasks(tasks) {
const queues = {};
const queries = [];
const otherTasks = [];

const counters = { active: 0, reserved: 0, waiting: 0 };

tasks.forEach((task) => {
queues[task.queue] = queues[task.queue] || { name: task.queue, active: 0, reserved: 0, waiting: 0 };
queues[task.queue][task.state] += 1;

if (task.enqueue_time) {
task.enqueue_time = moment(task.enqueue_time * 1000.0);
}
if (task.start_time) {
task.start_time = moment(task.start_time * 1000.0);
}

counters[task.state] += 1;

if (task.task_name === 'redash.tasks.execute_query') {
queries.push(task);
} else {
otherTasks.push(task);
}
});

return { queues: values(queues), queries, otherTasks, counters };
}

function QueuesTable({ loading, queues }) {
const columns = ['Name', 'Active', 'Reserved', 'Waiting'].map(c => ({ title: c, dataIndex: c.toLowerCase() }));

return <Table columns={columns} rowKey="name" dataSource={queues} loading={loading} />;
}

QueuesTable.propTypes = {
loading: PropTypes.bool.isRequired,
queues: PropTypes.arrayOf(PropTypes.any).isRequired,
};

function CounterCard({ title, value, loading }) {
return (
<Spin spinning={loading}>
<Card>
{title}
<div className="f-20">{value}</div>
</Card>
</Spin>
);
}

CounterCard.propTypes = {
title: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
loading: PropTypes.bool.isRequired,
};

CounterCard.defaultProps = {
value: '',
};

export default class AdminCeleryStatus extends React.Component {
state = {
loading: true,
error: false,
counters: {},
queries: [],
otherTasks: [],
queues: [],
};

constructor(props) {
super(props);
this.fetch();
}

fetch() {
// TODO: handle error
$http
.get('/api/admin/queries/tasks')
.then(({ data }) => {
const { queues, queries, otherTasks, counters } = parseTasks(data.tasks);
this.setState({ loading: false, queries, otherTasks, queues, counters });
})
.catch(() => {
this.setState({ loading: false, error: true });
});
}

render() {
const commonColumns = [
{
title: 'Worker Name',
dataIndex: 'worker',
},
{
title: 'PID',
dataIndex: 'worker_pid',
},
{
title: 'Queue',
dataIndex: 'queue',
},
{
title: 'State',
dataIndex: 'state',
render: (value) => {
if (value === 'active') {
return (
<span>
<Badge status="processing" /> Active
</span>
);
}
return (
<span>
<Badge status="warning" /> {value}
</span>
);
},
},
Columns.timeAgo({ title: 'Start Time', dataIndex: 'start_time' }),
];

const queryColumns = commonColumns.concat([
Columns.timeAgo({ title: 'Enqueue Time', dataIndex: 'enqueue_time' }),
{
title: 'Query ID',
dataIndex: 'query_id',
},
{
title: 'Org ID',
dataIndex: 'org_id',
},
{
title: 'Data Source ID',
dataIndex: 'data_source_id',
},
{
title: 'User ID',
dataIndex: 'user_id',
},
{
title: 'Scheduled',
dataIndex: 'scheduled',
},
]);

const otherTasksColumns = commonColumns.concat([
{
title: 'Task Name',
dataIndex: 'task_name',
},
]);

if (this.state.error) {
return (
<div className="p-5">
<Alert type="error" message="Failed loading status. Please refresh." />
</div>
);
}

return (
<div className="p-5">
<Row gutter={16}>
<Col span={4}>
<CounterCard title="Active Tasks" value={this.state.counters.active} loading={this.state.loading} />
</Col>
<Col span={4}>
<CounterCard title="Reserved Tasks" value={this.state.counters.reserved} loading={this.state.loading} />
</Col>
<Col span={4}>
<CounterCard title="Waiting Tasks" value={this.state.counters.waiting} loading={this.state.loading} />
</Col>
</Row>
<Row>
<Tabs defaultActiveKey="queues">
<Tabs.TabPane key="queues" tab="Queues">
<QueuesTable loading={this.state.loading} queues={this.state.queues} />
</Tabs.TabPane>
<Tabs.TabPane key="queries" tab="Queries">
<Table
rowKey="task_id"
dataSource={this.state.queries}
loading={this.state.loading}
columns={queryColumns}
/>
</Tabs.TabPane>
<Tabs.TabPane key="other" tab="Other Tasks">
<Table
rowKey="task_id"
dataSource={this.state.otherTasks}
loading={this.state.loading}
columns={otherTasksColumns}
/>
</Tabs.TabPane>
</Tabs>
</Row>
</div>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="bg-white tiled">
<ul class="tab-nav">
<li><a href="admin/status">System Status</a></li>
<li><a href="admin/queries/tasks">Queries Queue</a></li>
<li><a href="admin/queries/tasks">Celery Status</a></li>
<li class="active"><a href="admin/queries/outdated">Outdated Queries</a></li>
</ul>

Expand Down
32 changes: 20 additions & 12 deletions client/app/pages/admin/status/status.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,50 @@
<a href="admin/status">System Status</a>
</li>
<li>
<a href="admin/queries/tasks">Queries Queue</a>
<a href="admin/queries/tasks">Celery Status</a>
</li>
<li>
<a href="admin/queries/outdated">Outdated Queries</a>
</li>
</ul>


<div class="row p-15">
<ul class="list-group p-l-15 col-lg-4">
<li class="list-group-item active">General</li>
<li class="list-group-item" ng-repeat="(name, value) in status">
<span class="badge">{{value}}</span>
{{name | toHuman}}
<span class="badge">{{ value }}</span>
{{ name | toHuman }}
</li>
</ul>

<ul class="list-group col-lg-4">
<li class="list-group-item active">Manager</li>
<li class="list-group-item">
<span class="badge" am-time-ago="manager.last_refresh_at*1000.0"></span>
<span
class="badge"
am-time-ago="manager.last_refresh_at*1000.0"
></span>
Last Refresh
</li>
<li class="list-group-item">
<span class="badge" am-time-ago="manager.started_at*1000.0"></span>
Started
</li>
<li class="list-group-item">
<span class="badge">{{manager.outdated_queries_count}}</span>
<span class="badge">{{ manager.outdated_queries_count }}</span>
Outdated Queries Count
</li>
</ul>

<ul class="list-group col-lg-4">
<li class="list-group-item active">Queues</li>
<li class="list-group-item" ng-repeat="(name, value) in manager.queues">
<span class="badge">{{value.size}}</span>
{{name}}
<span uib-popover="{{value.data_sources}}" popover-trigger="'mouseenter'">
<span class="badge">{{ value.size }}</span>
{{ name }}
<span
uib-popover="{{ value.data_sources }}"
popover-trigger="'mouseenter'"
>
<i class="fa fa-question-circle"></i>
</span>
</li>
Expand All @@ -54,9 +59,12 @@
<div class="row p-15">
<ul class="list-group col-lg-4">
<li class="list-group-item active">Redash Database</li>
<li class="list-group-item" ng-repeat="(name, size) in database_metrics.metrics">
<span class="badge">{{size[1] | prettySize}}</span>
<span> {{size[0]}} </span>
<li
class="list-group-item"
ng-repeat="(name, size) in database_metrics.metrics"
>
<span class="badge">{{ size[1] | prettySize }}</span>
<span> {{ size[0] }} </span>
</li>
</ul>
</div>
Expand Down
Loading

0 comments on commit 26f0ce0

Please sign in to comment.