Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
Gutyán Gábor
/
circlestack
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
932191a6
authored
Sep 17, 2014
by
Bach Dániel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
dashboard: rewrite graph views
parent
113c28e4
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
306 additions
and
87 deletions
+306
-87
circle/dashboard/graph.py
+289
-0
circle/dashboard/urls.py
+9
-4
circle/dashboard/views.py
+0
-83
circle/vm/models/instance.py
+4
-0
circle/vm/models/node.py
+4
-0
No files found.
circle/dashboard/graph.py
0 → 100644
View file @
932191a6
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from
__future__
import
absolute_import
,
unicode_literals
import
logging
import
requests
from
django.conf
import
settings
from
django.core.exceptions
import
PermissionDenied
,
SuspiciousOperation
from
django.http
import
HttpResponse
,
Http404
from
django.views.generic
import
View
from
braces.views
import
LoginRequiredMixin
,
SuperuserRequiredMixin
from
vm.models
import
Instance
,
Node
logger
=
logging
.
getLogger
(
__name__
)
def
register_graph
(
metric_cls
,
graph_name
,
graphview_cls
):
if
not
hasattr
(
graphview_cls
,
'metrics'
):
graphview_cls
.
metrics
=
{}
graphview_cls
.
metrics
[
graph_name
]
=
metric_cls
class
GraphViewBase
(
LoginRequiredMixin
,
View
):
def
create_class
(
self
,
cls
):
return
type
(
str
(
cls
.
__name__
+
'Metric'
),
(
cls
,
self
.
base
),
{})
def
get
(
self
,
request
,
pk
,
metric
,
time
,
*
args
,
**
kwargs
):
graphite_url
=
settings
.
GRAPHITE_URL
if
graphite_url
is
None
:
raise
Http404
()
try
:
metric
=
self
.
metrics
[
metric
]
except
KeyError
:
raise
SuspiciousOperation
()
try
:
instance
=
self
.
get_object
(
request
,
pk
)
except
self
.
model
.
DoesNotExist
:
raise
Http404
()
metric
=
self
.
create_class
(
metric
)(
instance
)
return
HttpResponse
(
metric
.
get_graph
(
graphite_url
,
time
),
mimetype
=
"image/png"
)
def
get_object
(
self
,
request
,
pk
):
instance
=
self
.
model
.
objects
.
get
(
id
=
pk
)
if
not
instance
.
has_level
(
request
.
user
,
'user'
):
raise
PermissionDenied
()
return
instance
class
Metric
(
object
):
cacti_style
=
True
derivative
=
False
scale_to_seconds
=
None
metric_name
=
None
title
=
None
label
=
None
def
__init__
(
self
,
obj
,
metric_name
=
None
):
self
.
obj
=
obj
self
.
metric_name
=
(
metric_name
or
self
.
metric_name
or
self
.
__class__
.
__name__
.
lower
())
def
get_metric_name
(
self
):
return
self
.
metric_name
def
get_label
(
self
):
return
self
.
label
or
self
.
get_metric_name
()
def
get_title
(
self
):
return
self
.
title
or
self
.
get_metric_name
()
def
get_minmax
(
self
):
return
(
None
,
None
)
def
get_target
(
self
):
target
=
'
%
s.
%
s'
%
(
self
.
obj
.
metric_prefix
,
self
.
get_metric_name
())
if
self
.
derivative
:
target
=
'nonNegativeDerivative(
%
s)'
%
target
if
self
.
scale_to_seconds
:
target
=
'scaleToSeconds(
%
s,
%
d)'
%
(
target
,
self
.
scale_to_seconds
)
target
=
'alias(
%
s, "
%
s")'
%
(
target
,
self
.
get_label
())
if
self
.
cacti_style
:
target
=
'cactiStyle(
%
s)'
%
target
return
target
def
get_graph
(
self
,
graphite_url
,
time
,
width
=
500
,
height
=
200
):
params
=
{
'target'
:
self
.
get_target
(),
'from'
:
'-
%
s'
%
time
,
'title'
:
self
.
get_title
()
.
encode
(
'UTF-8'
),
'width'
:
width
,
'height'
:
height
}
ymin
,
ymax
=
self
.
get_minmax
()
if
ymin
is
not
None
:
params
[
'yMin'
]
=
ymin
if
ymax
is
not
None
:
params
[
'yMax'
]
=
ymax
logger
.
debug
(
'
%
s
%
s'
,
graphite_url
,
params
)
response
=
requests
.
get
(
'
%
s/render/'
%
graphite_url
,
params
=
params
)
return
response
.
content
class
VmMetric
(
Metric
):
def
get_title
(
self
):
title
=
super
(
VmMetric
,
self
)
.
get_title
()
return
'
%
s (
%
s) -
%
s'
%
(
self
.
obj
.
name
,
self
.
obj
.
vm_name
,
title
)
class
NodeMetric
(
Metric
):
def
get_title
(
self
):
title
=
super
(
NodeMetric
,
self
)
.
get_title
()
return
'
%
s (
%
s) -
%
s'
%
(
self
.
obj
.
name
,
self
.
obj
.
host
.
hostname
,
title
)
class
VmGraphView
(
GraphViewBase
):
model
=
Instance
base
=
VmMetric
class
NodeGraphView
(
SuperuserRequiredMixin
,
GraphViewBase
):
model
=
Node
base
=
NodeMetric
def
get_object
(
self
,
request
,
pk
):
return
self
.
model
.
objects
.
get
(
id
=
pk
)
class
NodeListGraphView
(
SuperuserRequiredMixin
,
GraphViewBase
):
model
=
Node
base
=
Metric
def
get_object
(
self
,
request
,
pk
):
return
Node
.
objects
.
filter
(
enabled
=
True
)
def
get
(
self
,
request
,
metric
,
time
,
*
args
,
**
kwargs
):
return
super
(
NodeListGraphView
,
self
)
.
get
(
request
,
None
,
metric
,
time
)
class
Ram
(
object
):
metric_name
=
"memory.usage"
title
=
"RAM usage (
%
)"
label
=
"RAM usage (
%
)"
def
get_minmax
(
self
):
return
(
0
,
105
)
register_graph
(
Ram
,
'memory'
,
VmGraphView
)
register_graph
(
Ram
,
'memory'
,
NodeGraphView
)
class
Cpu
(
object
):
metric_name
=
"cpu.percent"
title
=
"CPU usage (
%
)"
label
=
"CPU usage (
%
)"
def
get_minmax
(
self
):
if
isinstance
(
self
.
obj
,
Node
):
return
(
0
,
105
)
else
:
return
(
0
,
self
.
obj
.
num_cores
*
100
+
5
)
register_graph
(
Cpu
,
'cpu'
,
VmGraphView
)
register_graph
(
Cpu
,
'cpu'
,
NodeGraphView
)
class
VmNetwork
(
object
):
title
=
"Network"
def
get_minmax
(
self
):
return
(
0
,
None
)
def
get_target
(
self
):
metrics
=
[]
for
n
in
self
.
obj
.
interface_set
.
all
():
params
=
(
self
.
obj
.
metric_prefix
,
n
.
vlan
.
vid
,
n
.
vlan
.
name
)
metrics
.
append
(
'alias(scaleToSeconds(nonNegativeDerivative('
'
%
s.network.bytes_recv-
%
s), 10), "out -
%
s (bits/s)")'
%
(
params
))
metrics
.
append
(
'alias(scaleToSeconds(nonNegativeDerivative('
'
%
s.network.bytes_sent-
%
s), 10), "in -
%
s (bits/s)")'
%
(
params
))
return
'group(
%
s)'
%
','
.
join
(
metrics
)
register_graph
(
VmNetwork
,
'network'
,
VmGraphView
)
class
NodeNetwork
(
object
):
title
=
"Network"
def
get_minmax
(
self
):
return
(
0
,
None
)
def
get_target
(
self
):
return
(
'aliasSub(scaleToSeconds(nonNegativeDerivative(
%
s.network.b*),'
'10), ".*
\
.bytes_(sent|recv)-([a-zA-Z0-9]+).*", "
\\
2
\\
1")'
%
(
self
.
obj
.
metric_prefix
))
register_graph
(
NodeNetwork
,
'network'
,
NodeGraphView
)
class
NodeVms
(
object
):
metric_name
=
"vmcount"
title
=
"Instance count"
label
=
"instance count"
def
get_minmax
(
self
):
return
(
0
,
None
)
register_graph
(
NodeVms
,
'vm'
,
NodeGraphView
)
class
NodeAllocated
(
object
):
title
=
"Allocated memory (bytes)"
def
get_target
(
self
):
prefix
=
self
.
obj
.
metric_prefix
if
self
.
obj
.
online
:
ram_size
=
self
.
obj
.
ram_size
else
:
ram_size
=
0
used
=
'alias(
%
s.memory.used_bytes, "used")'
%
prefix
allocated
=
'alias(
%
s.memory.allocated, "allocated")'
%
prefix
max
=
'threshold(
%
d, "max")'
%
ram_size
return
'cactiStyle(group(
%
s,
%
s,
%
s))'
%
(
used
,
allocated
,
max
)
def
get_minmax
(
self
):
return
(
0
,
None
)
register_graph
(
NodeAllocated
,
'alloc'
,
NodeGraphView
)
class
NodeListAllocated
(
object
):
title
=
"Allocated memory (bytes)"
def
get_target
(
self
):
nodes
=
self
.
obj
used
=
','
.
join
(
'
%
s.memory.used_bytes'
%
n
.
metric_prefix
for
n
in
nodes
)
allocated
=
'alias(sumSeries(
%
s), "allocated")'
%
','
.
join
(
'
%
s.memory.allocated'
%
n
.
metric_prefix
for
n
in
nodes
)
max
=
'threshold(
%
d, "max")'
%
sum
(
n
.
ram_size
for
n
in
nodes
if
n
.
online
)
return
(
'group(aliasSub(aliasByNode(stacked(group(
%
s)), 1), "$",'
'" (used)"),
%
s,
%
s)'
%
(
used
,
allocated
,
max
))
def
get_minmax
(
self
):
return
(
0
,
None
)
register_graph
(
NodeListAllocated
,
'alloc'
,
NodeListGraphView
)
class
NodeListVms
(
object
):
title
=
"Instance count"
def
get_target
(
self
):
vmcount
=
','
.
join
(
'
%
s.vmcount'
%
n
.
metric_prefix
for
n
in
self
.
obj
)
return
'group(aliasByNode(stacked(group(
%
s)), 1))'
%
vmcount
def
get_minmax
(
self
):
return
(
0
,
None
)
register_graph
(
NodeListVms
,
'vm'
,
NodeListGraphView
)
circle/dashboard/urls.py
View file @
932191a6
...
...
@@ -20,16 +20,17 @@ from django.conf.urls import patterns, url, include
import
autocomplete_light
from
vm.models
import
Instance
from
.graph
import
VmGraphView
,
NodeGraphView
,
NodeListGraphView
from
.views
import
(
AclUpdateView
,
FavouriteView
,
GroupAclUpdateView
,
GroupDelete
,
GroupDetailView
,
GroupList
,
IndexView
,
InstanceActivityDetail
,
LeaseCreate
,
LeaseDelete
,
LeaseDetail
,
MyPreferencesView
,
NodeAddTraitView
,
NodeCreate
,
NodeDelete
,
NodeDetailView
,
NodeFlushView
,
Node
GraphView
,
Node
List
,
NodeStatus
,
NodeDetailView
,
NodeFlushView
,
NodeList
,
NodeStatus
,
NotificationView
,
PortDelete
,
TemplateAclUpdateView
,
TemplateCreate
,
TemplateDelete
,
TemplateDetail
,
TemplateList
,
TransferOwnershipConfirmView
,
TransferOwnershipView
,
vm_activity
,
VmCreate
,
VmDetailView
,
VmDetailVncTokenView
,
Vm
GraphView
,
Vm
List
,
VmDetailVncTokenView
,
VmList
,
DiskRemoveView
,
get_disk_download_status
,
InterfaceDeleteView
,
GroupRemoveUserView
,
GroupRemoveFutureUserView
,
...
...
@@ -121,14 +122,18 @@ urlpatterns = patterns(
name
=
"dashboard.views.delete-group"
),
url
(
r'^group/list/$'
,
GroupList
.
as_view
(),
name
=
'dashboard.views.group-list'
),
url
((
r'^vm/(?P<pk>\d+)/graph/(?P<metric>
cpu|memory|network
)/'
url
((
r'^vm/(?P<pk>\d+)/graph/(?P<metric>
[a-z]+
)/'
r'(?P<time>[0-9]{1,2}[hdwy])$'
),
VmGraphView
.
as_view
(),
name
=
'dashboard.views.vm-graph'
),
url
((
r'^node/(?P<pk>\d+)/graph/(?P<metric>
cpu|memory|network
)/'
url
((
r'^node/(?P<pk>\d+)/graph/(?P<metric>
[a-z]+
)/'
r'(?P<time>[0-9]{1,2}[hdwy])$'
),
NodeGraphView
.
as_view
(),
name
=
'dashboard.views.node-graph'
),
url
((
r'^node/graph/(?P<metric>[a-z]+)/'
r'(?P<time>[0-9]{1,2}[hdwy])$'
),
NodeListGraphView
.
as_view
(),
name
=
'dashboard.views.node-list-graph'
),
url
(
r'^group/(?P<pk>\d+)/$'
,
GroupDetailView
.
as_view
(),
name
=
'dashboard.views.group-detail'
),
url
(
r'^group/(?P<pk>\d+)/update/$'
,
GroupProfileUpdate
.
as_view
(),
...
...
circle/dashboard/views.py
View file @
932191a6
...
...
@@ -25,7 +25,6 @@ from urlparse import urljoin
import
json
import
logging
import
re
import
requests
from
django.conf
import
settings
from
django.contrib.auth.models
import
User
,
Group
...
...
@@ -2904,88 +2903,6 @@ class TransferOwnershipConfirmView(LoginRequiredMixin, View):
return
(
instance
,
new_owner
)
class
GraphViewBase
(
LoginRequiredMixin
,
View
):
def
get
(
self
,
request
,
pk
,
metric
,
time
,
*
args
,
**
kwargs
):
graphite_url
=
settings
.
GRAPHITE_URL
if
graphite_url
is
None
:
raise
Http404
()
if
metric
not
in
self
.
metrics
.
keys
():
raise
SuspiciousOperation
()
try
:
instance
=
self
.
get_object
(
request
,
pk
)
except
self
.
model
.
DoesNotExist
:
raise
Http404
()
prefix
=
self
.
get_prefix
(
instance
)
target
=
self
.
metrics
[
metric
]
%
{
'prefix'
:
prefix
}
title
=
self
.
get_title
(
instance
,
metric
)
params
=
{
'target'
:
target
,
'from'
:
'-
%
s'
%
time
,
'title'
:
title
.
encode
(
'UTF-8'
),
'width'
:
'500'
,
'height'
:
'200'
}
logger
.
debug
(
'
%
s
%
s'
,
graphite_url
,
params
)
response
=
requests
.
get
(
'
%
s/render/'
%
graphite_url
,
params
=
params
)
return
HttpResponse
(
response
.
content
,
mimetype
=
"image/png"
)
def
get_prefix
(
self
,
instance
):
raise
NotImplementedError
(
"Subclass must implement abstract method"
)
def
get_title
(
self
,
instance
,
metric
):
raise
NotImplementedError
(
"Subclass must implement abstract method"
)
def
get_object
(
self
,
request
,
pk
):
instance
=
self
.
model
.
objects
.
get
(
id
=
pk
)
if
not
instance
.
has_level
(
request
.
user
,
'user'
):
raise
PermissionDenied
()
return
instance
class
VmGraphView
(
GraphViewBase
):
metrics
=
{
'cpu'
:
(
'cactiStyle(alias(nonNegativeDerivative(
%(prefix)
s.cpu.usage),'
'"cpu usage (
%%
)"))'
),
'memory'
:
(
'cactiStyle(alias(
%(prefix)
s.memory.usage,'
'"memory usage (
%%
)"))'
),
'network'
:
(
'group('
'aliasSub(nonNegativeDerivative(
%(prefix)
s.network.bytes_recv*),'
' ".*-(
\
d+)
\\
)", "out (vlan
\\
1)"),'
'aliasSub(nonNegativeDerivative(
%(prefix)
s.network.bytes_sent*),'
' ".*-(
\
d+)
\\
)", "in (vlan
\\
1)"))'
),
}
model
=
Instance
def
get_prefix
(
self
,
instance
):
return
'vm.
%
s'
%
instance
.
vm_name
def
get_title
(
self
,
instance
,
metric
):
return
'
%
s (
%
s) -
%
s'
%
(
instance
.
name
,
instance
.
vm_name
,
metric
)
class
NodeGraphView
(
SuperuserRequiredMixin
,
GraphViewBase
):
metrics
=
{
'cpu'
:
(
'cactiStyle(alias(nonNegativeDerivative(
%(prefix)
s.cpu.times),'
'"cpu usage (
%%
)"))'
),
'memory'
:
(
'cactiStyle(alias(
%(prefix)
s.memory.usage,'
'"memory usage (
%%
)"))'
),
'network'
:
(
'cactiStyle(aliasByMetric('
'nonNegativeDerivative(
%(prefix)
s.network.bytes_*)))'
),
}
model
=
Node
def
get_prefix
(
self
,
instance
):
return
'circle.
%
s'
%
instance
.
host
.
hostname
def
get_title
(
self
,
instance
,
metric
):
return
'
%
s -
%
s'
%
(
instance
.
name
,
metric
)
def
get_object
(
self
,
request
,
pk
):
return
self
.
model
.
objects
.
get
(
id
=
pk
)
class
NotificationView
(
LoginRequiredMixin
,
TemplateView
):
def
get_template_names
(
self
):
...
...
circle/vm/models/instance.py
View file @
932191a6
...
...
@@ -1002,3 +1002,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
latest
=
self
.
get_latest_activity_in_progress
()
return
(
latest
and
latest
.
resultant_state
is
not
None
and
self
.
status
!=
latest
.
resultant_state
)
@property
def
metric_prefix
(
self
):
return
'vm.
%
s'
%
self
.
vm_name
circle/vm/models/node.py
View file @
932191a6
...
...
@@ -381,3 +381,7 @@ class Node(OperatedMixin, TimeStampedModel):
@permalink
def
get_absolute_url
(
self
):
return
(
'dashboard.views.node-detail'
,
None
,
{
'pk'
:
self
.
id
})
@property
def
metric_prefix
(
self
):
return
'circle.
%
s'
%
self
.
host
.
hostname
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment