#!/usr/bin/env python # # Copyright (c) 2014 eNovance # # 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. # # Tools to measure the duration of a get and a write request, can be used like: # # $ python duration_perf_test.py # # or to simulate multiple clients workload: # # $ clients=10 # $ parallel --progress -j $clients python duration_perf_test.py \ # --result myresults/client{} ::: $(seq 0 $clients) # $ python duration_perf_analyse.py myresults # * get_measures: # Time # count 1000.000000 # mean 0.032090 # std 0.028287 # ... # import argparse import datetime import json import os import random import time from keystoneclient.v2_0 import client as keystone_client import requests def timer(func): def inner(self, index, *args, **kwargs): start = time.time() count = func(self, index, *args, **kwargs) elapsed = time.time() - start self._timers.setdefault(func.__name__, []).append( (index, elapsed, count) ) print(("{name} #{index} processed " "{count} objects in {elapsed} sec").format( name=func.__name__, index=index, count=count or 0, elapsed=elapsed)) return count return inner class PerfTools(object): def __init__(self, args): self.args = args self.keystone = keystone_client.Client( username=args.username, password=args.password, tenant_name=args.tenant_name, auth_url=args.auth_url) self.headers = {'X-Auth-Token': self.keystone.auth_token, 'Content-Type': 'application/json'} self._metrics = [] self._timers = {} self.timestamp = datetime.datetime.utcnow() @timer def write_metric(self, index): data = json.dumps({"archive_policy_name": self.args.archive_policy}) resp = requests.post(self.args.gnocchi_url + "/v1/metric", data=data, headers=self.headers) try: self._metrics.append(json.loads(resp.content)["id"]) except Exception: raise RuntimeError("Can't continue without all metrics created " "(%s)" % resp.content) @timer def write_measures(self, index, metric): data = [] for i in range(self.args.batch_size): self.timestamp += datetime.timedelta(minutes=1) data.append({'timestamp': self.timestamp.isoformat(), 'value': 100}) resp = requests.post( "%s/v1/metric/%s/measures" % (self.args.gnocchi_url, metric), data=json.dumps(data), headers=self.headers) if resp.status_code / 100 != 2: print('Failed POST request to measures #%d: %s' % (index, resp.content)) return 0 return self.args.batch_size @timer def get_measures(self, index, metric): resp = requests.get( "%s/v1/metric/%s/measures" % (self.args.gnocchi_url, metric), headers=self.headers) try: return len(json.loads(resp.content)) except Exception: print('Failed GET request to measures #%d: %s' % (index, resp.content)) return 0 def _get_random_metric(self): return self._metrics[random.randint(0, len(self._metrics) - 1)] def run(self): try: for index in range(self.args.metric_count): self.write_metric(index) for index in range(self.args.measure_count): metric = self._get_random_metric() self.write_measures(index, metric) self.get_measures(index, metric) finally: self.dump_logs() def dump_logs(self): for name, data in self._timers.items(): filepath = "%s_%s.csv" % (self.args.result_path, name) dirpath = os.path.dirname(filepath) if dirpath and not os.path.exists(dirpath): os.makedirs(dirpath) with open(filepath, 'w') as f: f.write("Index,Duration,Count\n") for meter in data: f.write("%s\n" % ",".join("%.2f" % (m if m else 0) for m in meter)) def main(): parser = argparse.ArgumentParser() parser.add_argument("--metric-count", help=('Number of metrics to be created. ' 'metrics are created one by one.'), default=100, type=int) parser.add_argument("--measure-count", help='Number of measures batches to be sent.', default=100, type=int) parser.add_argument("--gnocchi-url", help='Gnocchi API URL to use.', default="http://localhost:8041") parser.add_argument("--archive-policy", help='Archive policy to use.', default="low") parser.add_argument("--os-username", dest='username', help='User name to use for OpenStack service access.', default="admin") parser.add_argument("--os-tenant-name", dest='tenant_name', help=('Tenant name to use for ' 'OpenStack service access.'), default="admin") parser.add_argument("--os-password", dest='password', help='Password to use for OpenStack service access.', default="password") parser.add_argument("--os-auth-url", dest='auth_url', help='Auth URL to use for OpenStack service access.', default="http://localhost:5000/v2.0") parser.add_argument("--result", help='path prefix to write results to.', dest='result_path', default="./perf_gnocchi") parser.add_argument("--batch-size", dest='batch_size', help='Number of measurements in the batch.', default=100, type=int) PerfTools(parser.parse_args()).run() if __name__ == '__main__': main()