Skip to content

Commit 5f24503

Browse files
committedSep 11, 2017
Enhanced Metric Middleware.
Simplified metric development. Added more information into metrics. Changed metric format: Now is possible to send metrics directly to logstash. Better extensibility. Bug fixes: No messages lost when the connection to rabbitmq fails. Now it stops sending metrics when there are no requests.
1 parent 82b83dd commit 5f24503

26 files changed

+556
-707
lines changed
 

‎README.md

+41-62
Original file line numberDiff line numberDiff line change
@@ -103,90 +103,69 @@ sudo swift-init object restart
103103
Crystal Metric Middleware is an extension point of Crystal: new metrics can be added in order to provide more information to the controller.
104104
This repository includes some [metric samples](/metric_samples) like the number of GET/PUT operations, the number of active operations, the bandwidth used or the request performance.
105105

106-
The code below is an example of a simple metric that counts GET requests:
106+
Metric classes can implement the `on_start()`, `on_read()` and `on_finish()` methods.
107+
108+
* `on_start()`: This method is called when the requests starts, only once.
109+
110+
* `on_read(chunk)`: this method is called each time a chunk is read from the main stream. The read chunk enters to the method in order to be able to operate over it.
111+
112+
* `on_finish()`: This method is called when the requests finishes, only once.
113+
114+
There are three types of metrics supported:
115+
116+
* `stateless`: the default type. When the metric value is published, the metric value is reset to 0.
117+
118+
* `stateful`: when the metric is published, the value is not reset. Thus, the metric value can be incremented/decremented during the next intervals. In an below example, a stateful metric is used to count the active requests.
119+
120+
* `force`: this type of metric is published directly after the call to register_metric instead of being published at intervals.
121+
122+
Metric classes must be registered and enabled through [Crystal controller API](https://github.com/Crystal-SDS/controller/).
123+
A convenient [web dashboard](https://github.com/Crystal-SDS/dashboard) is also available to simplify these API calls.
124+
125+
126+
## Examples
127+
The code below is an example of a simple metric that counts GET requests per second.:
107128

108129
```python
109130
from crystal_metric_middleware.metrics.abstract_metric import AbstractMetric
110131

111132
# The metric class inherits from AbstractMetric
112-
class GetOps(AbstractMetric):
113-
114-
# This method must be implemented because is the main interception point.
115-
def execute(self):
116-
# Setting the type of the metric. The metric internal value is reset when it is
117-
# published (at periodic intervals)
118-
self.type = 'stateless'
119-
120-
# Checking if it's a GET operation and an object request
121-
if self.method == "GET" and self._is_object_request():
122-
# register_metric(key, value) increments by one the current metric (GetOps)
123-
# for the current target tenant
124-
self.register_metric(self.account, 1)
125-
126-
return self.response
133+
class OperationsPerSecond(AbstractMetric):
134+
135+
self.type = 'stateless'
136+
137+
on_start(self):
138+
self.register_metric(1)
127139
```
128140

129-
The code below is an example of a bandwidth metric for GET object requests:
141+
The code below is an example of a bandwidth metric that counts the number of MBytes per second:
130142

131143
```python
132-
class GetBw(AbstractMetric):
144+
class Bandwidth(AbstractMetric):
145+
146+
self.type = 'stateless'
133147

134-
def execute(self):
135-
self.type = 'stateless'
136-
137-
if self.method == "GET" and self._is_object_request():
138-
# By calling _intercept_get(), all read chunks will enter on_read method
139-
self._intercept_get()
140-
141-
# If the request is intercepted with _intercept_get() it is necessary to return the response
142-
return self.response
143-
144148
def on_read(self, chunk):
145-
# In this case, the metric counts the number of bytes
146149
mbytes = (len(chunk)/1024.0)/1024
147150
self.register_metric(self.account, mbytes)
148151
```
149152

150-
The next example shows a metric that counts active PUT requests:
153+
The next example shows a metric that counts active requests: whenever a request is intercepted, the metric value is incremented, and when the request finishes, the metric value is decremented. At periodic intervals the metric value is published, showing how many concurrent requests are being served at a given instant.
151154

152155
```python
153-
class PutActiveRequests(AbstractMetric):
156+
class ActiveRequests(AbstractMetric):
154157

155-
def execute(self):
156-
# The type is stateful: the internal value is never reset
157-
self.type = 'stateful'
158-
159-
if self.method == "PUT" and self._is_object_request():
160-
self._intercept_put()
161-
# Incrementing the metric (a new active PUT request has been intercepted)
162-
self.register_metric(self.account, 1)
163-
164-
return self.request
158+
self.type = 'stateful'
159+
160+
def on_start(self):
161+
# Incrementing the metric
162+
self.register_metric(+1)
165163

166164
def on_finish(self):
167-
# Decrementing the metric (the PUT request has just finished)
168-
self.register_metric(self.account, -1)
165+
# Decrementing the metric
166+
self.register_metric(-1)
169167
```
170168

171-
Metric classes (inheriting from AbstractMetric) must implement the `execute()` method and can implement the optional `on_read()` and `on_finish()` methods.
172-
173-
* `execute()`: This method is the main interception point. If the metric needs access to the data flow or needs to know when the request has finished, it has to call `self._intercept_get()` or `self._intercept_put()`.
174-
175-
* `on_read()`: this method is called if the metric has previously called `self._intercept_get()` or `self._intercept_put()`. All read chunks will enter this method.
176-
177-
* `on_finish()`: this method is called if the metric has previously called `self._intercept_get()` or `self._intercept_put()`. This method is called once the request has been completed.
178-
179-
There are three types of metrics supported:
180-
181-
* `stateless`: the default type. When the metric is published (typically at one second intervals), the internal value is reset to 0.
182-
183-
* `stateful`: when the metric is published, the internal value is not reset. Thus, the value can be incremented/decremented during the next intervals. In a previous example, a stateful metric is used to count the active PUT requests: whenever a request is intercepted the metric value is incremented, and when the request finishes the metric value is decremented. At periodic intervals the metric value is published, showing how many concurrent requests are being served at a given instant.
184-
185-
* `force`: this type of metric is published directly after the call to register_metric instead of being published at intervals.
186-
187-
Metric classes must be registered and enabled through [Crystal controller API](https://github.com/Crystal-SDS/controller/).
188-
A convenient [web dashboard](https://github.com/iostackproject/SDS-dashboard) is also available to simplify these API calls.
189-
190169
## Support
191170

192171
Please [open an issue](https://github.com/Crystal-SDS/metric-middleware/issues/new) for support.

‎crystal_metric_middleware/crystal_metric_control.py

-267
This file was deleted.

‎crystal_metric_middleware/crystal_metric_handler.py ‎crystal_metric_middleware/metric_handler.py

+46-49
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from crystal_metric_control import CrystalMetricControl
1+
from metrics.metric_control import CrystalMetricControl
22
from swift.common.swob import HTTPInternalServerError
33
from swift.common.swob import HTTPException
44
from swift.common.swob import wsgify
@@ -90,58 +90,56 @@ def _import_metric(self, metric):
9090
self.exec_server, self.request, self.response)
9191
return metric_class
9292

93-
@property
94-
def _is_valid_request(self):
95-
return self.method == 'GET' or self.method == 'PUT'
96-
9793
def handle_request(self):
98-
99-
if self._is_valid_request:
10094
metrics = self.crystal_control.get_metrics()
10195

102-
for metric_key in metrics:
103-
metric = metrics[metric_key]
104-
if metric['in_flow'] == 'True':
105-
metric_class = self._import_metric(metric)
106-
self.request = metric_class.execute()
107-
108-
if hasattr(self.request.environ['wsgi.input'], 'metrics'):
109-
metric_list = list()
110-
for metric in self.request.environ['wsgi.input'].metrics:
111-
metric_list.append(metric.metric_name)
112-
self.logger.info('Go to execute metrics on input flow: ' +
113-
str(metric_list))
114-
115-
self.response = self.request.get_response(self.app)
116-
117-
if self.response.is_success:
96+
if self.method == 'PUT':
11897
for metric_key in metrics:
11998
metric = metrics[metric_key]
120-
if metric['out_flow'] == 'True':
99+
if metric['in_flow'] == 'True':
121100
metric_class = self._import_metric(metric)
122-
self.response = metric_class.execute()
101+
self.request = metric_class.execute()
123102

124-
if hasattr(self.response.app_iter, 'metrics'):
125-
metric_list = list()
126-
for metric in self.response.app_iter.metrics:
127-
metric_list.append(metric.metric_name)
128-
self.logger.info('Go to execute metrics on output flow: ' +
129-
str(metric_list))
103+
if hasattr(self.request.environ['wsgi.input'], 'metrics'):
104+
metric_list = list()
105+
for metric in self.request.environ['wsgi.input'].metrics:
106+
metric_list.append(metric.metric_name)
107+
self.logger.info('Go to execute metrics on PUT request: ' +
108+
str(metric_list))
130109

131-
return self.response
110+
return self.request.get_response(self.app)
132111

133-
return self.request.get_response(self.app)
112+
elif self.method == 'GET':
113+
self.response = self.request.get_response(self.app)
134114

115+
if self.response.is_success:
116+
for metric_key in metrics:
117+
metric = metrics[metric_key]
118+
if metric['out_flow'] == 'True':
119+
metric_class = self._import_metric(metric)
120+
self.response = metric_class.execute()
135121

136-
class CrystalMetricHandlerMiddleware(object):
122+
if hasattr(self.response.app_iter, 'metrics'):
123+
metric_list = list()
124+
for metric in self.response.app_iter.metrics:
125+
metric_list.append(metric.metric_name)
126+
self.logger.info('Go to execute metrics on GET request: ' +
127+
str(metric_list))
137128

138-
def __init__(self, app, conf, crystal_conf):
129+
return self.response
130+
else:
131+
return self.request.get_response(self.app)
132+
133+
134+
class CrystalMetricMiddleware(object):
135+
136+
def __init__(self, app, conf):
139137
self.app = app
140138
self.exec_server = conf.get('execution_server')
141139
self.logger = get_logger(conf, name=self.exec_server +
142140
"-server Crystal Metrics",
143141
log_route='crystal_metric_handler')
144-
self.conf = crystal_conf
142+
self.conf = conf
145143
self.handler_class = CrystalMetricHandler
146144
self.control_class = CrystalMetricControl
147145

@@ -177,23 +175,22 @@ def filter_factory(global_conf, **local_conf):
177175
conf = global_conf.copy()
178176
conf.update(local_conf)
179177

180-
crystal_conf = dict()
181-
crystal_conf['execution_server'] = conf.get('execution_server', 'proxy')
178+
conf['execution_server'] = conf.get('execution_server', 'proxy')
179+
conf['execution_server'] = conf.get('region_id', 1)
180+
conf['execution_server'] = conf.get('zone_id', 1)
182181

183-
crystal_conf['rabbit_host'] = conf.get('rabbit_host', 'controller')
184-
crystal_conf['rabbit_port'] = int(conf.get('rabbit_port', 5672))
185-
crystal_conf['rabbit_username'] = conf.get('rabbit_username', 'openstack')
186-
crystal_conf['rabbit_password'] = conf.get('rabbit_password', 'openstack')
182+
conf['rabbit_host'] = conf.get('rabbit_host', 'controller')
183+
conf['rabbit_port'] = int(conf.get('rabbit_port', 5672))
184+
conf['rabbit_username'] = conf.get('rabbit_username', 'openstack')
185+
conf['rabbit_password'] = conf.get('rabbit_password', 'openstack')
187186

188-
crystal_conf['redis_host'] = conf.get('redis_host', 'controller')
189-
crystal_conf['redis_port'] = int(conf.get('redis_port', 6379))
190-
crystal_conf['redis_db'] = int(conf.get('redis_db', 0))
187+
conf['redis_host'] = conf.get('redis_host', 'controller')
188+
conf['redis_port'] = int(conf.get('redis_port', 6379))
189+
conf['redis_db'] = int(conf.get('redis_db', 0))
191190

192-
crystal_conf['bind_ip'] = conf.get('bind_ip')
193-
crystal_conf['bind_port'] = conf.get('bind_port')
194-
crystal_conf['devices'] = conf.get('devices', '/srv/node')
191+
conf['devices'] = conf.get('devices', '/srv/node')
195192

196193
def swift_crystal_metric_middleware(app):
197-
return CrystalMetricHandlerMiddleware(app, conf, crystal_conf)
194+
return CrystalMetricMiddleware(app, conf)
198195

199196
return swift_crystal_metric_middleware

‎crystal_metric_middleware/metrics/abstract_metric.py

+33-31
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88

99
class AbstractMetric(object):
10+
11+
type = 'stateless'
12+
1013
def __init__(self, logger, crystal_control, metric_name, server,
1114
request, response):
1215
self.logger = logger
@@ -16,40 +19,31 @@ def __init__(self, logger, crystal_control, metric_name, server,
1619
self.metric_name = metric_name
1720
self.current_server = server
1821
self.method = self.request.method
19-
self.type = 'stateless'
2022
self.read_timeout = 30 # seconds
2123

2224
self._parse_vaco()
23-
self.account_name = self.request.headers['X-Project-Name']
24-
self.account = self.account_name + "#:#" + self.account_id
25-
self.account_and_container = self.account_name + "/" + self.container + "#:#" + self.account_id + "/" + self.container
2625

27-
def register_metric(self, key, value):
26+
self.project_name = str(self.request.headers['X-Project-Name'])
27+
self.data = dict()
28+
self.data['project'] = self.project_name
29+
self.data['container'] = os.path.join(self.project_name, self.container)
30+
self.data['method'] = self.method
31+
32+
def register_metric(self, value):
2833
"""
2934
Send data to publish thread
3035
"""
31-
routing_key = 'metrics.'+self.metric_name
36+
metric_name = self.metric_name
37+
self.data['value'] = value
3238
if self.type == 'stateful':
33-
self.crystal_control.publish_stateful_metric(routing_key,
34-
key, value)
39+
self.crystal_control.publish_stateful_metric(metric_name,
40+
self.data)
3541
elif self.type == 'stateless':
36-
self.crystal_control.publish_stateless_metric(routing_key,
37-
key, value)
42+
self.crystal_control.publish_stateless_metric(metric_name,
43+
self.data)
3844
elif self.type == 'force':
39-
self.crystal_control.force_publish_metric(routing_key,
40-
key, value)
41-
42-
def _is_object_request(self):
43-
if self.current_server == 'proxy':
44-
path = self.request.environ['PATH_INFO']
45-
if path.endswith('/'):
46-
path = path[:-1]
47-
splitted_path = path.split('/')
48-
if len(splitted_path) > 4:
49-
return True
50-
else:
51-
# TODO: Check for object-server
52-
return True
45+
self.crystal_control.force_publish_metric(metric_name,
46+
self.data)
5347

5448
def _is_get_already_intercepted(self):
5549
return isinstance(self.response.app_iter, IterLikeFileDescriptor) or \
@@ -116,15 +110,23 @@ def _intercept_put(self):
116110
self.request.environ['wsgi.input'] = IterLikePut(reader, metrics, self.read_timeout)
117111

118112
def _parse_vaco(self):
119-
if self._is_object_request():
120-
if self.current_server == 'proxy':
121-
_, self.account_id, self.container, self.object = self.request.split_path(4, 4, rest_with_last=True)
122-
else:
123-
_, _, self.account_id, self.container, self.object = self.request.split_path(5, 5, rest_with_last=True)
113+
if self.current_server == 'proxy':
114+
_, self.account_id, self.container, self.object = self.request.split_path(4, 4, rest_with_last=True)
115+
else:
116+
_, _, self.account_id, self.container, self.object = self.request.split_path(5, 5, rest_with_last=True)
124117

125118
def execute(self):
126-
""" Execute Metric """
127-
raise NotImplementedError()
119+
if self.method == "GET":
120+
self._intercept_get()
121+
self.on_start()
122+
return self.response
123+
elif self.method == "PUT":
124+
self._intercept_put()
125+
self.on_start()
126+
return self.request
127+
128+
def on_start(self):
129+
pass
128130

129131
def on_read(self, chunk):
130132
pass
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,385 @@
1+
from threading import Thread
2+
from datetime import datetime, timedelta
3+
from eventlet import greenthread
4+
import Queue
5+
import socket
6+
import time
7+
import pytz
8+
import pika
9+
import redis
10+
import json
11+
import copy
12+
import os
13+
14+
15+
SRC_METRIC_PATH = os.path.join("/opt", "crystal", "workload_metrics")
16+
DST_METRIC_PATH = os.path.abspath(__file__).rsplit('/', 1)[0]+'/metrics'
17+
18+
19+
class Singleton(type):
20+
_instances = {}
21+
22+
def __call__(cls, *args, **kwargs): # @NoSelf
23+
if cls not in cls._instances:
24+
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
25+
return cls._instances[cls]
26+
27+
28+
class CrystalMetricControl(object):
29+
__metaclass__ = Singleton
30+
31+
def __init__(self, conf, log):
32+
self.logger = log
33+
self.conf = conf
34+
35+
self.status_thread = NodeStatusThread(self.conf, self.logger)
36+
self.status_thread.daemon = True
37+
self.status_thread.start()
38+
39+
self.control_thread = ControlThread(self.conf, self.logger)
40+
self.control_thread.daemon = True
41+
42+
self.publish_thread = PublishThread(self.conf, self.logger)
43+
self.publish_thread.daemon = True
44+
45+
self.threads_started = False
46+
47+
def get_metrics(self):
48+
return self.control_thread.metric_list
49+
50+
def publish_stateful_metric(self, routing_key, data):
51+
self.publish_thread.publish_statefull(routing_key, data)
52+
53+
def publish_stateless_metric(self, routing_key, data):
54+
self.publish_thread.publish_stateless(routing_key, data)
55+
56+
def force_publish_metric(self, routing_key, data):
57+
self.publish_thread.force_publish_metric(routing_key, data)
58+
59+
60+
class PublishThread(Thread):
61+
62+
def __init__(self, conf, logger):
63+
Thread.__init__(self)
64+
65+
self.logger = logger
66+
self.monitoring_statefull_data = dict()
67+
self.monitoring_stateless_data = dict()
68+
self.messages_to_send = Queue.Queue()
69+
70+
self.interval = conf.get('publish_interval', 0.995)
71+
# self.ip = conf.get('bind_ip')+":"+conf.get('bind_port')
72+
self.host_name = socket.gethostname()
73+
self.exchange = conf.get('exchange', 'amq.topic')
74+
75+
rabbit_host = conf.get('rabbit_host')
76+
rabbit_port = int(conf.get('rabbit_port'))
77+
rabbit_user = conf.get('rabbit_username')
78+
rabbit_pass = conf.get('rabbit_password')
79+
80+
credentials = pika.PlainCredentials(rabbit_user, rabbit_pass)
81+
self.parameters = pika.ConnectionParameters(host=rabbit_host,
82+
port=rabbit_port,
83+
credentials=credentials)
84+
85+
def publish_statefull(self, metric_name, data):
86+
if metric_name not in self.monitoring_statefull_data:
87+
self.monitoring_statefull_data[metric_name] = dict()
88+
89+
value = data['value']
90+
del data['value']
91+
key = str(data)
92+
93+
if key not in self.monitoring_statefull_data[metric_name]:
94+
self.monitoring_statefull_data[metric_name][key] = 0
95+
96+
try:
97+
self.monitoring_statefull_data[metric_name][key] += value
98+
except Exception as e:
99+
print e
100+
101+
def publish_stateless(self, metric_name, data):
102+
if metric_name not in self.monitoring_stateless_data:
103+
self.monitoring_stateless_data[metric_name] = dict()
104+
105+
value = data['value']
106+
del data['value']
107+
key = str(data)
108+
109+
if key not in self.monitoring_stateless_data[metric_name]:
110+
self.monitoring_stateless_data[metric_name][key] = 0
111+
112+
try:
113+
self.monitoring_stateless_data[metric_name][key] += value
114+
except Exception as e:
115+
print e
116+
117+
def force_publish_metric(self, metric_name, data):
118+
date = datetime.now(pytz.timezone(time.tzname[0]))
119+
120+
data['host'] = self.host_name
121+
data['metric_name'] = metric_name
122+
data['@timestamp'] = str(date.isoformat())
123+
124+
routing_key = 'metric.'+data['method'].lower()+'_'+metric_name
125+
message = dict()
126+
message[routing_key] = data
127+
self.messages_to_send.put(message)
128+
129+
def _generate_messages_from_stateless_data(self, date, last_date):
130+
stateless_data_copy = copy.deepcopy(self.monitoring_stateless_data)
131+
132+
if last_date == date.strftime("%Y-%m-%d %H:%M:%S"):
133+
self.last_stateless_data = copy.deepcopy(stateless_data_copy)
134+
return
135+
136+
for metric_name in stateless_data_copy.keys():
137+
for key in stateless_data_copy[metric_name].keys():
138+
# example: {"{'project': 'crystal', 'container': 'crystal/data_1', 'method': 'GET'}": 52,
139+
# "{'project': 'crystal', 'container': 'crystal/data_2', 'method': 'PUT'}": 31}
140+
141+
if metric_name not in self.zero_value_timeout:
142+
self.zero_value_timeout[metric_name] = dict()
143+
if key not in self.zero_value_timeout[metric_name]:
144+
self.zero_value_timeout[metric_name][key] = 0
145+
146+
if self.last_stateless_data and \
147+
metric_name in self.last_stateless_data and \
148+
key in self.last_stateless_data[metric_name]:
149+
value = stateless_data_copy[metric_name][key] - \
150+
self.last_stateless_data[metric_name][key]
151+
else:
152+
# send value = 0 for second-1 for pretty printing into Kibana
153+
pre_data = eval(key)
154+
pre_data['host'] = self.host_name
155+
pre_data['metric_name'] = metric_name
156+
d = date - timedelta(seconds=1)
157+
pre_data['@timestamp'] = str(d.isoformat())
158+
pre_data['value'] = 0
159+
160+
routing_key = 'metric.'+pre_data['method'].lower()+'_'+metric_name
161+
message = dict()
162+
message[routing_key] = pre_data
163+
self.messages_to_send.put(message)
164+
165+
value = stateless_data_copy[metric_name][key]
166+
167+
if value == 0.0:
168+
self.zero_value_timeout[metric_name][key] += 1
169+
if self.zero_value_timeout[metric_name][key] == 5:
170+
del self.monitoring_stateless_data[metric_name][key]
171+
if len(self.monitoring_stateless_data[metric_name]) == 0:
172+
del self.monitoring_stateless_data[metric_name]
173+
del self.last_stateless_data[metric_name][key]
174+
if len(self.last_stateless_data[metric_name]) == 0:
175+
del self.last_stateless_data[metric_name]
176+
del self.zero_value_timeout[metric_name][key]
177+
if len(self.zero_value_timeout[metric_name]) == 0:
178+
del self.zero_value_timeout[metric_name]
179+
else:
180+
self.zero_value_timeout[metric_name][key] = 0
181+
182+
data = eval(key)
183+
data['host'] = self.host_name
184+
data['metric_name'] = metric_name
185+
data['@timestamp'] = str(date.isoformat())
186+
data['value'] = value
187+
188+
routing_key = 'metric.'+data['method'].lower()+'_'+metric_name
189+
message = dict()
190+
message[routing_key] = data
191+
self.messages_to_send.put(message)
192+
193+
self.last_stateless_data = copy.deepcopy(stateless_data_copy)
194+
195+
def _generate_messages_from_statefull_data(self, date, last_date):
196+
197+
if last_date == date.strftime("%Y-%m-%d %H:%M:%S"):
198+
return
199+
200+
statefull_data_copy = copy.deepcopy(self.monitoring_statefull_data)
201+
202+
for metric_name in statefull_data_copy.keys():
203+
for key in statefull_data_copy[metric_name].keys():
204+
205+
if metric_name not in self.zero_value_timeout:
206+
self.zero_value_timeout[metric_name] = dict()
207+
if key not in self.zero_value_timeout[metric_name]:
208+
self.zero_value_timeout[metric_name][key] = 0
209+
210+
if self.last_statefull_data and \
211+
metric_name in self.last_statefull_data and \
212+
key in self.last_statefull_data[metric_name]:
213+
value = statefull_data_copy[metric_name][key]
214+
else:
215+
# send value = 0 for second-1 for pretty printing into Kibana
216+
pre_data = eval(key)
217+
pre_data['host'] = self.host_name
218+
pre_data['metric_name'] = metric_name
219+
d = date - timedelta(seconds=1)
220+
pre_data['@timestamp'] = str(d.isoformat())
221+
pre_data['value'] = 0
222+
223+
routing_key = 'metric.'+pre_data['method'].lower()+'_'+metric_name
224+
message = dict()
225+
message[routing_key] = pre_data
226+
self.messages_to_send.put(message)
227+
228+
value = statefull_data_copy[metric_name][key]
229+
230+
if value == 0:
231+
self.zero_value_timeout[metric_name][key] += 1
232+
if self.zero_value_timeout[metric_name][key] == 5:
233+
del self.monitoring_statefull_data[metric_name][key]
234+
if len(self.monitoring_statefull_data[metric_name]) == 0:
235+
del self.monitoring_statefull_data[metric_name]
236+
del self.last_statefull_data[metric_name][key]
237+
if len(self.last_statefull_data[metric_name]) == 0:
238+
del self.last_statefull_data[metric_name]
239+
del self.zero_value_timeout[metric_name][key]
240+
if len(self.zero_value_timeout[metric_name]) == 0:
241+
del self.zero_value_timeout[metric_name]
242+
else:
243+
self.zero_value_timeout[metric_name][key] = 0
244+
245+
data = eval(key)
246+
data['host'] = self.host_name
247+
data['metric_name'] = metric_name
248+
data['@timestamp'] = str(date.isoformat())
249+
data['value'] = value
250+
251+
routing_key = 'metric.'+data['method'].lower()+'_'+metric_name
252+
message = dict()
253+
message[routing_key] = data
254+
self.messages_to_send.put(message)
255+
256+
self.last_statefull_data = copy.deepcopy(statefull_data_copy)
257+
258+
def run(self):
259+
last_date = None
260+
self.last_stateless_data = None
261+
self.last_statefull_data = None
262+
self.zero_value_timeout = dict()
263+
264+
self.rabbit = pika.BlockingConnection(self.parameters)
265+
self.channel = self.rabbit.channel()
266+
267+
while True:
268+
greenthread.sleep(self.interval)
269+
date = datetime.now(pytz.timezone(time.tzname[0]))
270+
self._generate_messages_from_stateless_data(date, last_date)
271+
self._generate_messages_from_statefull_data(date, last_date)
272+
last_date = date.strftime("%Y-%m-%d %H:%M:%S")
273+
274+
try:
275+
while not self.messages_to_send.empty():
276+
message = self.messages_to_send.get()
277+
for routing_key in message:
278+
data = message[routing_key]
279+
self.channel.basic_publish(exchange=self.exchange,
280+
routing_key=routing_key,
281+
body=json.dumps(data))
282+
except:
283+
self.messages_to_send.put(message)
284+
self.rabbit = pika.BlockingConnection(self.parameters)
285+
self.channel = self.rabbit.channel()
286+
287+
288+
class ControlThread(Thread):
289+
290+
def __init__(self, conf, logger):
291+
Thread.__init__(self)
292+
293+
self.conf = conf
294+
self.logger = logger
295+
self.server = self.conf.get('execution_server')
296+
self.interval = self.conf.get('control_interval', 10)
297+
self.redis_host = self.conf.get('redis_host')
298+
self.redis_port = self.conf.get('redis_port')
299+
self.redis_db = self.conf.get('redis_db')
300+
301+
self.redis = redis.StrictRedis(self.redis_host,
302+
self.redis_port,
303+
self.redis_db)
304+
305+
self.metric_list = {}
306+
307+
def _get_workload_metrics(self):
308+
"""
309+
This method connects to redis to download the metrics and the
310+
information introduced via the dashboard.
311+
"""
312+
metric_keys = self.redis.keys("workload_metric:*")
313+
metric_list = dict()
314+
for key in metric_keys:
315+
metric = self.redis.hgetall(key)
316+
if metric['execution_server'] == self.server and \
317+
metric['enabled'] == 'True':
318+
metric_list[key] = metric
319+
320+
return metric_list
321+
322+
def run(self):
323+
while True:
324+
try:
325+
self.metric_list = self._get_workload_metrics()
326+
except:
327+
self.logger.error("Unable to connect to " + self.redis_host +
328+
" for getting the workload metrics.")
329+
greenthread.sleep(self.interval)
330+
331+
332+
class NodeStatusThread(Thread):
333+
334+
def __init__(self, conf, logger):
335+
Thread.__init__(self)
336+
337+
self.conf = conf
338+
self.logger = logger
339+
self.server = self.conf.get('execution_server')
340+
self.region_id = self.conf.get('region_id')
341+
self.zone_id = self.conf.get('zone_id')
342+
self.interval = self.conf.get('status_interval', 10)
343+
self.redis_host = self.conf.get('redis_host')
344+
self.redis_port = self.conf.get('redis_port')
345+
self.redis_db = self.conf.get('redis_db')
346+
347+
self.host_name = socket.gethostname()
348+
self.host_ip = socket.gethostbyname(self.host_name)
349+
self.devices = self.conf.get('devices')
350+
351+
self.redis = redis.StrictRedis(self.redis_host,
352+
self.redis_port,
353+
self.redis_db)
354+
355+
self.metric_list = {}
356+
357+
def _get_swift_disk_usage(self):
358+
swift_devices = dict()
359+
if self.server == 'object':
360+
if self.devices and os.path.exists(self.devices):
361+
for disk in os.listdir(self.devices):
362+
if disk.startswith('sd'):
363+
statvfs = os.statvfs(self.devices+'/'+disk)
364+
swift_devices[disk] = dict()
365+
swift_devices[disk]['size'] = statvfs.f_frsize * statvfs.f_blocks
366+
swift_devices[disk]['free'] = statvfs.f_frsize * statvfs.f_bfree
367+
368+
return swift_devices
369+
370+
def run(self):
371+
while True:
372+
try:
373+
swift_usage = self._get_swift_disk_usage()
374+
self.redis.hmset(self.server+'_node:'+self.host_name,
375+
{'type': self.server,
376+
'name': self.host_name,
377+
'ip': self.host_ip,
378+
'region_id': self.region_id,
379+
'zone_id': self.zone_id,
380+
'last_ping': time.time(),
381+
'devices': json.dumps(swift_usage)})
382+
except:
383+
self.logger.error("Unable to connect to " + self.redis_host +
384+
" for publishing the node status.")
385+
greenthread.sleep(self.interval)

‎metric_samples/active_requests.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from crystal_metric_middleware.metrics.abstract_metric import AbstractMetric
2+
3+
4+
class ActiveRequests(AbstractMetric):
5+
6+
type = 'stateful'
7+
8+
def on_start(self):
9+
self.register_metric(+1)
10+
11+
def on_finish(self):
12+
self.register_metric(-1)

‎metric_samples/bandwidth.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from crystal_metric_middleware.metrics.abstract_metric import AbstractMetric
2+
3+
4+
class Bandwidth(AbstractMetric):
5+
6+
type = 'stateless'
7+
8+
def on_read(self, chunk):
9+
mbytes = (len(chunk)/1024.0)/1024
10+
self.register_metric(mbytes)

‎metric_samples/container/get_active_requests_container.py

-19
This file was deleted.

‎metric_samples/container/get_bw_container.py

-20
This file was deleted.

‎metric_samples/container/get_ops_container.py

-13
This file was deleted.

‎metric_samples/container/get_request_performance_container.py

-22
This file was deleted.

‎metric_samples/container/put_active_requests_container.py

-19
This file was deleted.

‎metric_samples/container/put_bw_container.py

-20
This file was deleted.

‎metric_samples/container/put_ops_container.py

-13
This file was deleted.

‎metric_samples/container/put_request_performance_container.py

-22
This file was deleted.
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from crystal_metric_middleware.metrics.abstract_metric import AbstractMetric
2+
3+
4+
class OperationsPerSecond(AbstractMetric):
5+
6+
type = 'stateless'
7+
8+
def on_start(self):
9+
self.register_metric(1)

‎metric_samples/request_performance.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from crystal_metric_middleware.metrics.abstract_metric import AbstractMetric
2+
import time
3+
4+
5+
class RequestPerformance(AbstractMetric):
6+
7+
type = 'force'
8+
9+
def on_start(self):
10+
self.start_time = time.time()
11+
self.request_size = 0
12+
13+
def on_read(self, chunk):
14+
self.request_size += len(chunk)
15+
16+
def on_finish(self):
17+
transfer_perf = ((self.request_size/(time.time()-self.start_time))/1024.0)/1024
18+
self.register_metric(transfer_perf)

‎metric_samples/tenant/get_active_requests.py

-19
This file was deleted.

‎metric_samples/tenant/get_bw.py

-20
This file was deleted.

‎metric_samples/tenant/get_ops.py

-13
This file was deleted.

‎metric_samples/tenant/get_request_performance.py

-22
This file was deleted.

‎metric_samples/tenant/put_active_requests.py

-19
This file was deleted.

‎metric_samples/tenant/put_bw.py

-20
This file was deleted.

‎metric_samples/tenant/put_ops.py

-13
This file was deleted.

‎metric_samples/tenant/put_request_performance.py

-22
This file was deleted.

‎setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from setuptools import setup, find_packages
22

33
paste_factory = ['crystal_metric_handler = '
4-
'crystal_metric_middleware.crystal_metric_handler:filter_factory']
4+
'crystal_metric_middleware.metric_handler:filter_factory']
55

66
setup(name='swift_crystal_metric_middleware',
7-
version='0.1.0',
7+
version='0.7.3',
88
description='Crystal metric middleware for OpenStack Swift',
99
author='The AST-IOStack Team: Josep Sampe, Raul Gracia',
1010
url='http://iostack.eu',

0 commit comments

Comments
 (0)
Please sign in to comment.