Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / admin / stats.py @ bda47e03

History | View | Annotate | Download (9.3 kB)

1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34

    
35
import itertools
36
import operator
37
import datetime
38

    
39
from collections import defaultdict  # , OrderedDict
40
from copy import copy
41
from django.conf import settings
42
from django.db.models import Count, Sum
43

    
44
from snf_django.lib.astakos import UserCache
45
from synnefo.db.models import VirtualMachine, Network, Backend
46
from synnefo.api.util import get_image
47
from synnefo.logic import backend as backend_mod
48

    
49

    
50
def get_cyclades_stats(backend=None, clusters=True, servers=True,
51
                       resources=True, networks=True, images=True):
52
    stats = {"datetime": datetime.datetime.now().strftime("%c")}
53
    if clusters:
54
        stats["clusters"] = get_cluster_stats(backend=backend)
55
    if servers:
56
        stats["servers"] = get_servers_stats(backend=backend)
57
    if resources:
58
        stats["resources"] = get_resources_stats(backend=backend)
59
    if networks:
60
        stats["networks"] = get_networks_stats()
61
    if images:
62
        stats["images"] = get_images_stats(backend=None)
63
    return stats
64

    
65

    
66
def get_cluster_stats(backend):
67
    total = Backend.objects.all()
68
    stats = {"total": total.count(),
69
             "drained": total.filter(drained=True).count(),
70
             "offline": total.filter(offline=True).count()}
71
    return stats
72

    
73

    
74
def _get_total_servers(backend=None):
75
    total_servers = VirtualMachine.objects.all()
76
    if backend is not None:
77
        total_servers = total_servers.filter(backend=backend)
78
    return total_servers
79

    
80

    
81
def get_servers_stats(backend=None):
82
    total_servers = _get_total_servers(backend=backend)
83
    per_state = total_servers.values("operstate")\
84
                             .annotate(count=Count("operstate"))
85
    stats = {"total": 0}
86
    [stats.setdefault(s[0], 0) for s in VirtualMachine.OPER_STATES]
87
    for x in per_state:
88
        stats[x["operstate"]] = x["count"]
89
        stats["total"] += x["count"]
90
    return stats
91

    
92

    
93
def get_resources_stats(backend=None):
94
    total_servers = _get_total_servers(backend=backend)
95
    active_servers = total_servers.filter(deleted=False)
96

    
97
    allocated = {}
98
    server_count = {}
99
    for res in ["cpu", "ram", "disk", "disk_template"]:
100
        server_count[res] = {}
101
        allocated[res] = 0
102
        val = "flavor__%s" % res
103
        results = active_servers.values(val).annotate(count=Count(val))
104
        for result in results:
105
            server_count[res][result[val]] = result["count"]
106
            if res != "disk_template":
107
                allocated[res] += result["count"]
108

    
109
    resources_stats = get_backend_stats(backend=backend)
110
    for res in ["cpu", "ram", "disk", "disk_template"]:
111
        if res not in resources_stats:
112
            resources_stats[res] = {}
113
        resources_stats[res]["servers"] = server_count[res]
114
        resources_stats[res]["allocated"] = allocated[res]
115

    
116
    return resources_stats
117

    
118

    
119
def get_images_stats(backend=None):
120
    total_servers = _get_total_servers(backend=backend)
121
    active_servers = total_servers.filter(deleted=False)
122

    
123
    active_servers_images = active_servers.values("imageid", "userid")\
124
                                          .annotate(number=Count("imageid"))
125
    image_cache = ImageCache()
126
    image_stats = defaultdict(int)
127
    for result in active_servers_images:
128
        imageid = image_cache.get_image(result["imageid"], result["userid"])
129
        image_stats[imageid] += result["number"]
130
    return dict(image_stats)
131

    
132

    
133
def get_networks_stats():
134
    total_networks = Network.objects.all()
135
    stats = {"public_ips": get_ip_stats(),
136
             "total": 0}
137
    per_state = total_networks.values("state")\
138
                              .annotate(count=Count("state"))
139
    [stats.setdefault(s[0], 0) for s in Network.OPER_STATES]
140
    for x in per_state:
141
        stats[x["state"]] = x["count"]
142
        stats["total"] += x["count"]
143
    return stats
144

    
145

    
146
def group_by_resource(objects, resource):
147
    stats = {}
148
    key = operator.attrgetter("flavor."+resource)
149
    grouped = itertools.groupby(sorted(objects, key=key), key)
150
    for val, group in grouped:
151
        stats[val] = len(list(group))
152
    return stats
153

    
154

    
155
def get_ip_stats():
156
    total, free = 0, 0,
157
    for network in Network.objects.filter(public=True, deleted=False):
158
        try:
159
            net_total, net_free = network.ip_count()
160
        except AttributeError:
161
            # TODO: Check that this works..
162
            pool = network.get_pool(locked=False)
163
            net_total = pool.pool_size
164
            net_free = pool.count_available()
165
        if not network.drained:
166
            total += net_total
167
            free += net_free
168
    return {"total": total,
169
            "free": free}
170

    
171

    
172
def get_backend_stats(backend=None):
173
    if backend is None:
174
        backends = Backend.objects.filter(offline=False)
175
    else:
176
        if backend.offline:
177
            return {}
178
        backends = [backend]
179
    [backend_mod.update_backend_resources(b) for b in backends]
180
    resources = {}
181
    for attr in ("dfree", "dtotal", "mfree", "mtotal", "ctotal"):
182
        resources[attr] = 0
183
        for b in backends:
184
            resources[attr] += getattr(b, attr)
185

    
186
    return {"disk": {"free": resources["dfree"], "total": resources["dtotal"]},
187
            "ram": {"free": resources["mfree"], "total": resources["mtotal"]},
188
            "cpu": {"free": resources["ctotal"], "total": resources["ctotal"]},
189
            "disk_template": {"free": 0, "total": 0}}
190

    
191

    
192
class ImageCache(object):
193
    def __init__(self):
194
        self.images = {}
195
        usercache = UserCache(settings.ASTAKOS_BASE_URL,
196
                              settings.CYCLADES_SERVICE_TOKEN)
197
        self.system_user_uuid = \
198
            usercache.get_uuid(settings.SYSTEM_IMAGES_OWNER)
199

    
200
    def get_image(self, imageid, userid):
201
        if not imageid in self.images:
202
            try:
203
                image = get_image(imageid, userid)
204
                owner = image["owner"]
205
                owner = "system" if image["owner"] == self.system_user_uuid\
206
                        else "user"
207
                self.images[imageid] = owner + ":" + image["name"]
208
            except Exception:
209
                self.images[imageid] = "unknown:unknown"
210

    
211
        return self.images[imageid]
212

    
213

    
214
def get_public_stats():
215
    # VirtualMachines
216
    vm_objects = VirtualMachine.objects
217
    servers = vm_objects.values("deleted", "operstate")\
218
                        .annotate(count=Count("id"),
219
                                  cpu=Sum("flavor__cpu"),
220
                                  ram=Sum("flavor__ram"),
221
                                  disk=Sum("flavor__disk"))
222
    zero_stats = {"count": 0, "cpu": 0, "ram": 0, "disk": 0}
223
    server_stats = {}
224
    for state in VirtualMachine.RSAPI_STATE_FROM_OPER_STATE.values():
225
        server_stats[state] = copy(zero_stats)
226

    
227
    for stats in servers:
228
        deleted = stats.pop("deleted")
229
        operstate = stats.pop("operstate")
230
        state = VirtualMachine.RSAPI_STATE_FROM_OPER_STATE.get(operstate)
231
        if deleted:
232
            for key in zero_stats.keys():
233
                server_stats["DELETED"][key] += stats.get(key, 0)
234
        elif state:
235
            for key in zero_stats.keys():
236
                server_stats[state][key] += stats.get(key, 0)
237

    
238
    #Networks
239
    net_objects = Network.objects
240
    networks = net_objects.values("deleted", "state")\
241
                          .annotate(count=Count("id"))
242
    zero_stats = {"count": 0}
243
    network_stats = {}
244
    for state in Network.RSAPI_STATE_FROM_OPER_STATE.values():
245
        network_stats[state] = copy(zero_stats)
246

    
247
    for stats in networks:
248
        deleted = stats.pop("deleted")
249
        state = stats.pop("state")
250
        state = Network.RSAPI_STATE_FROM_OPER_STATE.get(state)
251
        if deleted:
252
            for key in zero_stats.keys():
253
                network_stats["DELETED"][key] += stats.get(key, 0)
254
        elif state:
255
            for key in zero_stats.keys():
256
                network_stats[state][key] += stats.get(key, 0)
257

    
258
    statistics = {"servers": server_stats,
259
                  "networks": network_stats}
260
    return statistics