summaryrefslogtreecommitdiff
path: root/nova/compute/stats.py
blob: cfbee2e6bc17be852a75065809c53db2328e1990 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# Copyright (c) 2012 OpenStack Foundation
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

from nova.compute import task_states
from nova.compute import vm_states
from nova.i18n import _


class Stats(dict):
    """Handler for updates to compute node workload stats."""

    def __init__(self):
        super(Stats, self).__init__()

        # Track instance states for compute node workload calculations:
        self.states = {}

    def clear(self):
        super(Stats, self).clear()

        self.states.clear()

    def digest_stats(self, stats):
        """Apply stats provided as a dict or a json encoded string."""
        if stats is None:
            return
        if isinstance(stats, dict):
            self.update(stats)
            return
        raise ValueError(_('Unexpected type adding stats'))

    @property
    def io_workload(self):
        """Calculate an I/O based load by counting I/O heavy operations."""

        def _get(state, state_type):
            key = "num_%s_%s" % (state_type, state)
            return self.get(key, 0)

        num_builds = _get(vm_states.BUILDING, "vm")
        num_migrations = _get(task_states.RESIZE_MIGRATING, "task")
        num_rebuilds = _get(task_states.REBUILDING, "task")
        num_resizes = _get(task_states.RESIZE_PREP, "task")
        num_snapshots = _get(task_states.IMAGE_SNAPSHOT, "task")
        num_backups = _get(task_states.IMAGE_BACKUP, "task")
        num_rescues = _get(task_states.RESCUING, "task")
        num_unshelves = _get(task_states.UNSHELVING, "task")

        return (num_builds + num_rebuilds + num_resizes + num_migrations +
                num_snapshots + num_backups + num_rescues + num_unshelves)

    def calculate_workload(self):
        """Calculate current load of the compute host based on
        task states.
        """
        current_workload = 0
        for k in self:
            if k.startswith("num_task") and not k.endswith("None"):
                current_workload += self[k]
        return current_workload

    @property
    def num_instances(self):
        return self.get("num_instances", 0)

    def num_instances_for_project(self, project_id):
        key = "num_proj_%s" % project_id
        return self.get(key, 0)

    def num_os_type(self, os_type):
        key = "num_os_type_%s" % os_type
        return self.get(key, 0)

    def update_stats_for_instance(self, instance, is_removed=False):
        """Update stats after an instance is changed."""

        uuid = instance['uuid']

        # First, remove stats from the previous instance
        # state:
        if uuid in self.states:
            old_state = self.states[uuid]

            self._decrement("num_vm_%s" % old_state['vm_state'])
            self._decrement("num_task_%s" % old_state['task_state'])
            self._decrement("num_os_type_%s" % old_state['os_type'])
            self._decrement("num_proj_%s" % old_state['project_id'])
        else:
            # new instance
            self._increment("num_instances")

        # Now update stats from the new instance state:
        (vm_state, task_state, os_type, project_id) = \
                self._extract_state_from_instance(instance)

        if is_removed or vm_state in vm_states.ALLOW_RESOURCE_REMOVAL:
            self._decrement("num_instances")
            self.states.pop(uuid)
        else:
            self._increment("num_vm_%s" % vm_state)
            self._increment("num_task_%s" % task_state)
            self._increment("num_os_type_%s" % os_type)
            self._increment("num_proj_%s" % project_id)

        # save updated I/O workload in stats:
        self["io_workload"] = self.io_workload

    def _decrement(self, key):
        x = self.get(key, 0)
        self[key] = x - 1

    def _increment(self, key):
        x = self.get(key, 0)
        self[key] = x + 1

    def _extract_state_from_instance(self, instance):
        """Save the useful bits of instance state for tracking purposes."""

        uuid = instance['uuid']
        vm_state = instance['vm_state']
        task_state = instance['task_state']
        os_type = instance['os_type']
        project_id = instance['project_id']

        self.states[uuid] = dict(vm_state=vm_state, task_state=task_state,
                                 os_type=os_type, project_id=project_id)

        return (vm_state, task_state, os_type, project_id)

    def build_failed(self):
        self['failed_builds'] = self.get('failed_builds', 0) + 1

    def build_succeeded(self):
        # FIXME(danms): Make this more graceful, either by time-based aging or
        # a fixed decline upon success
        self['failed_builds'] = 0