Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Celery/Queries Execution Status API #3057

Merged
merged 28 commits into from
Mar 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bee2670
Remove QueryTaskTracker
arikfr Nov 8, 2018
15d05c0
Remove scheudling of cleanup_tasks
arikfr Nov 8, 2018
c8270eb
Add Celery introspection tools
arikfr Nov 8, 2018
517b437
First iteration of updating the admin API.
arikfr Nov 8, 2018
6409d29
Show more details
arikfr Nov 8, 2018
25a15f7
Add option to skip building npm in Dockerfile
arikfr Nov 8, 2018
ad00770
Show started_at
arikfr Nov 8, 2018
522aebf
update the refresh schedule, as it's too fast
arikfr Nov 8, 2018
5aa001e
Update Celery monitor to report on all active tasks.
arikfr Nov 14, 2018
e1e499e
Merge branch 'master' into celery-introspection
arikfr Feb 17, 2019
81fda79
Merge branch 'master' into celery-introspection
arikfr Feb 17, 2019
3710358
Update task parsing for new format
arikfr Feb 17, 2019
e467817
WIP: improved celery status screen
arikfr Feb 17, 2019
d129c24
Fix property name.
arikfr Feb 17, 2019
6ca92e1
Update counters
arikfr Feb 19, 2019
bb55042
Merge branch 'master' into celery-introspection
arikfr Feb 27, 2019
4147f0d
Merge branch 'celery-introspection' of github.com:getredash/redash in…
arikfr Feb 27, 2019
b558b3b
Update tab name
arikfr Feb 27, 2019
3e1a302
Update counters names
arikfr Feb 27, 2019
3c8b208
Move component to its own file and fix lint issues
arikfr Feb 27, 2019
dc684d7
Add migratin to remove Redis keys
arikfr Feb 27, 2019
7efa30c
Improve columns layout
arikfr Feb 27, 2019
5726b3d
Remove skip_npm_build arg as it's not used anymore.
arikfr Mar 4, 2019
212dcbb
Convert query from SQL to Python
arikfr Mar 7, 2019
dfc5e26
Merge branch 'celery-introspection' of github.com:getredash/redash in…
arikfr Mar 7, 2019
254106f
Simplify column definition.
arikfr Mar 7, 2019
b0f2248
Show alert on error.
arikfr Mar 7, 2019
693451f
Merge branch 'master' into celery-introspection
arikfr Mar 7, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
arikfr marked this conversation as resolved.
Show resolved Hide resolved
$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',
arikfr marked this conversation as resolved.
Show resolved Hide resolved
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