Coverage for nova/compute/stats.py: 93%

82 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-17 15:08 +0000

1# Copyright (c) 2012 OpenStack Foundation 

2# All Rights Reserved. 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); you may 

5# not use this file except in compliance with the License. You may obtain 

6# a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

13# License for the specific language governing permissions and limitations 

14# under the License. 

15 

16from nova.compute import task_states 

17from nova.compute import vm_states 

18from nova.i18n import _ 

19 

20 

21class Stats(dict): 

22 """Handler for updates to compute node workload stats.""" 

23 

24 def __init__(self): 

25 super(Stats, self).__init__() 

26 

27 # Track instance states for compute node workload calculations: 

28 self.states = {} 

29 

30 def clear(self): 

31 super(Stats, self).clear() 

32 

33 self.states.clear() 

34 

35 def digest_stats(self, stats): 

36 """Apply stats provided as a dict or a json encoded string.""" 

37 if stats is None: 37 ↛ 39line 37 didn't jump to line 39 because the condition on line 37 was always true

38 return 

39 if isinstance(stats, dict): 

40 self.update(stats) 

41 return 

42 raise ValueError(_('Unexpected type adding stats')) 

43 

44 @property 

45 def io_workload(self): 

46 """Calculate an I/O based load by counting I/O heavy operations.""" 

47 

48 def _get(state, state_type): 

49 key = "num_%s_%s" % (state_type, state) 

50 return self.get(key, 0) 

51 

52 num_builds = _get(vm_states.BUILDING, "vm") 

53 num_migrations = _get(task_states.RESIZE_MIGRATING, "task") 

54 num_rebuilds = _get(task_states.REBUILDING, "task") 

55 num_resizes = _get(task_states.RESIZE_PREP, "task") 

56 num_snapshots = _get(task_states.IMAGE_SNAPSHOT, "task") 

57 num_backups = _get(task_states.IMAGE_BACKUP, "task") 

58 num_rescues = _get(task_states.RESCUING, "task") 

59 num_unshelves = _get(task_states.UNSHELVING, "task") 

60 

61 return (num_builds + num_rebuilds + num_resizes + num_migrations + 

62 num_snapshots + num_backups + num_rescues + num_unshelves) 

63 

64 def calculate_workload(self): 

65 """Calculate current load of the compute host based on 

66 task states. 

67 """ 

68 current_workload = 0 

69 for k in self: 

70 if k.startswith("num_task") and not k.endswith("None"): 

71 current_workload += self[k] 

72 return current_workload 

73 

74 @property 

75 def num_instances(self): 

76 return self.get("num_instances", 0) 

77 

78 def num_instances_for_project(self, project_id): 

79 key = "num_proj_%s" % project_id 

80 return self.get(key, 0) 

81 

82 def num_os_type(self, os_type): 

83 key = "num_os_type_%s" % os_type 

84 return self.get(key, 0) 

85 

86 def update_stats_for_instance(self, instance, is_removed=False): 

87 """Update stats after an instance is changed.""" 

88 

89 uuid = instance['uuid'] 

90 

91 # First, remove stats from the previous instance 

92 # state: 

93 if uuid in self.states: 

94 old_state = self.states[uuid] 

95 

96 self._decrement("num_vm_%s" % old_state['vm_state']) 

97 self._decrement("num_task_%s" % old_state['task_state']) 

98 self._decrement("num_os_type_%s" % old_state['os_type']) 

99 self._decrement("num_proj_%s" % old_state['project_id']) 

100 else: 

101 # new instance 

102 self._increment("num_instances") 

103 

104 # Now update stats from the new instance state: 

105 (vm_state, task_state, os_type, project_id) = \ 

106 self._extract_state_from_instance(instance) 

107 

108 if is_removed or vm_states.allow_resource_removal( 

109 vm_state=vm_state, task_state=task_state): 

110 self._decrement("num_instances") 

111 self.states.pop(uuid) 

112 else: 

113 self._increment("num_vm_%s" % vm_state) 

114 self._increment("num_task_%s" % task_state) 

115 self._increment("num_os_type_%s" % os_type) 

116 self._increment("num_proj_%s" % project_id) 

117 

118 # save updated I/O workload in stats: 

119 self["io_workload"] = self.io_workload 

120 

121 def _decrement(self, key): 

122 x = self.get(key, 0) 

123 self[key] = x - 1 

124 

125 def _increment(self, key): 

126 x = self.get(key, 0) 

127 self[key] = x + 1 

128 

129 def _extract_state_from_instance(self, instance): 

130 """Save the useful bits of instance state for tracking purposes.""" 

131 

132 uuid = instance['uuid'] 

133 vm_state = instance['vm_state'] 

134 task_state = instance['task_state'] 

135 os_type = instance['os_type'] 

136 project_id = instance['project_id'] 

137 

138 self.states[uuid] = dict(vm_state=vm_state, task_state=task_state, 

139 os_type=os_type, project_id=project_id) 

140 

141 return (vm_state, task_state, os_type, project_id) 

142 

143 def build_failed(self): 

144 self['failed_builds'] = self.get('failed_builds', 0) + 1 

145 

146 def build_succeeded(self): 

147 # FIXME(danms): Make this more graceful, either by time-based aging or 

148 # a fixed decline upon success 

149 self['failed_builds'] = 0