Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (9.5 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.plankton.utils import image_backend
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

    
126
    image_cache = ImageCache()
127
    image_stats = defaultdict(int)
128
    for result in active_servers_images:
129
        imageid = image_cache.get_image(result["imageid"], result["userid"])
130
        image_stats[imageid] += result["number"]
131
    return dict(image_stats)
132

    
133

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

    
146

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

    
155

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

    
172

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

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

    
192

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

    
201
    def get_image(self, imageid, userid):
202
        if not imageid in self.images:
203
            try:
204
                with image_backend(userid) as ib:
205
                    image = ib.get_image(imageid)
206
                properties = image.get("properties")
207
                os = properties.get("os",
208
                                    properties.get("osfamily", "unknown"))
209
                owner = image["owner"]
210
                owner = "system" if image["owner"] == self.system_user_uuid\
211
                        else "user"
212
                self.images[imageid] = owner + ":" + os
213
            except Exception:
214
                self.images[imageid] = "unknown:unknown"
215

    
216
        return self.images[imageid]
217

    
218

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

    
232
    for stats in servers:
233
        deleted = stats.pop("deleted")
234
        operstate = stats.pop("operstate")
235
        state = VirtualMachine.RSAPI_STATE_FROM_OPER_STATE.get(operstate)
236
        if deleted:
237
            for key in zero_stats.keys():
238
                server_stats["DELETED"][key] += stats.get(key, 0)
239
        elif state:
240
            for key in zero_stats.keys():
241
                server_stats[state][key] += stats.get(key, 0)
242

    
243
    #Networks
244
    net_objects = Network.objects
245
    networks = net_objects.values("deleted", "state")\
246
                          .annotate(count=Count("id"))
247
    zero_stats = {"count": 0}
248
    network_stats = {}
249
    for state in Network.RSAPI_STATE_FROM_OPER_STATE.values():
250
        network_stats[state] = copy(zero_stats)
251

    
252
    for stats in networks:
253
        deleted = stats.pop("deleted")
254
        state = stats.pop("state")
255
        state = Network.RSAPI_STATE_FROM_OPER_STATE.get(state)
256
        if deleted:
257
            for key in zero_stats.keys():
258
                network_stats["DELETED"][key] += stats.get(key, 0)
259
        elif state:
260
            for key in zero_stats.keys():
261
                network_stats[state][key] += stats.get(key, 0)
262

    
263
    statistics = {"servers": server_stats,
264
                  "networks": network_stats}
265
    return statistics