diff --git a/monitoring/lastcomment-scoreboard/.gitignore b/monitoring/lastcomment-scoreboard/.gitignore new file mode 100644 index 0000000..a8b70d7 --- /dev/null +++ b/monitoring/lastcomment-scoreboard/.gitignore @@ -0,0 +1,2 @@ +*.pyc +*.json diff --git a/monitoring/lastcomment-scoreboard/LICENSE b/monitoring/lastcomment-scoreboard/LICENSE new file mode 100644 index 0000000..ad410e1 --- /dev/null +++ b/monitoring/lastcomment-scoreboard/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. \ No newline at end of file diff --git a/monitoring/lastcomment-scoreboard/README.rst b/monitoring/lastcomment-scoreboard/README.rst new file mode 100644 index 0000000..9c808f6 --- /dev/null +++ b/monitoring/lastcomment-scoreboard/README.rst @@ -0,0 +1,76 @@ +Last Comment +============ + +Last Comment is a small script to query OpenStack's gerrit REST API +and produce a report about the status of CI systems. + +This can also be used as a CLI tool and used against any type of user (human or bot). + +Design +------- + +The html based report is a data file plus a static page. + + +lastcomment is powered by gerrit's REST API and doesn't use any datastore. +Although this means it cannot show results in real time, the data can be +refreshed as frequent as desired. + +Dependencies +------------ + +`requests` + +Help +----- + + ./lastcomment.py -h + +Generate a Report +------------------ + + +To generate a html report for third party CI accounts on http://localhost:8000/report: + + ./lastcomment.py -f ci.yaml -c 100 --json lastcomment.json + python -m SimpleHTTPServer + +Cloud-init +----------- + +To run this on a cloud server using cloud-init and cron use the ``user-data.txt`` file. + +Other Uses +---------- + +To see the last time the user 'Third Party CI' commented anywhere + + ./lastcomment.py -n 'Third Party CI' + +To print the last 30 comments by 'Third Party CI' on the repo openstack/cinder + + ./lastcomment.py -n 'Third Party CI' -m -p openstack/cinder + + +To print the last 30 votes by 'Third Party CI' on the repo openstack/cinder + + ./lastcomment.py -n 'Third Party CI' -v -p openstack/cinder + +To print the contents of the last 30 reviews by 'John Smith' + + ./lastcomment.py -n 'John Smith' -m + +To specify a yaml file names.yaml containing projects and names to iterate through + + ./lastcomment.py -f names.yaml + +To print statistics on third party CI accounts: + + ./lastcomment.py -c 100 -f ci.yaml -v + +To generate a html report for cinder's third party CI accounts on http://localhost:8000/report: + + ./lastcomment.py -f ci.yaml -c 100 --json lastcomment.json + python -m SimpleHTTPServer + + diff --git a/monitoring/lastcomment-scoreboard/ci.yaml b/monitoring/lastcomment-scoreboard/ci.yaml new file mode 100644 index 0000000..5e568b3 --- /dev/null +++ b/monitoring/lastcomment-scoreboard/ci.yaml @@ -0,0 +1,51 @@ +openstack/nova: + - VMware NSX CI + - Microsoft Hyper-V CI + - Citrix XenServer CI + - IBM PowerKVM CI + - Virtuozzo Storage CI + - XenProject CI + - DB Datasets CI + - Jenkins +openstack/cinder: + - IBM DB2 CI + - Pure Storage CI + - Microsoft iSCSI CI + - Tintri CI + - Blockbridge EPS CI + - Scality CI + - SolidFire CI + - NetApp CI + - Virtuozzo Storage CI + - StorPool distributed storage CI + - Dell Storage CI + - Datera CI + - HP Storage CI + - NetApp FC CI + - Violin Memory V6000 CI + - CloudByte CI + - Nexenta CI + - HDS HNAS CI + - EMC VNX CI + - VEDAMS DOTHILLDRIVER CI + - Hitachi HBSD CI + - Jenkins +openstack/neutron: + - A10 Networks CI + - Arista CI + - Big Switch CI + - Freescale CI + - HP Networking CI + - IBM SDN-VE CI + - Mellanox CI + - Metaplugin CI + - Microsoft Hyper-V CI + - Nuage CI + - OpenContrail CI + - Ryu CI + - VMware NSX CI + - Jenkins +openstack/manila: + - Jenkins + - NetApp CI + - HP Storage CI diff --git a/monitoring/lastcomment-scoreboard/lastcomment.py b/monitoring/lastcomment-scoreboard/lastcomment.py new file mode 100755 index 0000000..0029044 --- /dev/null +++ b/monitoring/lastcomment-scoreboard/lastcomment.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python + +"""Print the last time a reviewer(bot) left a comment.""" + +import argparse +import calendar +import collections +import datetime +import json +import sys +import yaml + +import requests + + +TIME_FORMAT = "%Y-%m-%d %H:%M:%S" + + +class Comment(object): + date = None + number = None + subject = None + now = None + + def __init__(self, date, number, subject, message): + super(Comment, self).__init__() + self.date = date + self.number = number + self.subject = subject + self.message = message + self.now = datetime.datetime.utcnow().replace(microsecond=0) + + def __str__(self): + return ("%s (%s old) https://review.openstack.org/%s '%s' " % ( + self.date.strftime(TIME_FORMAT), + self.age(), + self.number, self.subject)) + + def age(self): + return self.now - self.date + + def __le__(self, other): + # self < other + return self.date < other.date + + def __repr__(self): + # for sorting + return repr((self.date, self.number)) + + +def get_comments(change, name): + """Generator that returns all comments by name on a given change.""" + body = None + for message in change['messages']: + if 'author' in message and message['author']['name'] == name: + if (message['message'].startswith("Uploaded patch set") and + len(message['message'].split()) is 4): + # comment is auto created from posting a new patch + continue + date = message['date'] + body = message['message'] + # https://review.openstack.org/Documentation/rest-api.html#timestamp + # drop nanoseconds + date = date.split('.')[0] + date = datetime.datetime.strptime(date, TIME_FORMAT) + yield date, body + + +def query_gerrit(name, count, project): + # Include review messages in query + search = "reviewer:\"%s\"" % name + if project: + search = search + (" AND project:\"%s\"" % project) + query = ("https://review.openstack.org/changes/?q=%s&" + "o=MESSAGES" % search) + r = requests.get(query) + try: + changes = json.loads(r.text[4:]) + except ValueError: + print "query: '%s' failed with:\n%s" % (query, r.text) + sys.exit(1) + + comments = [] + for change in changes: + for date, message in get_comments(change, name): + if date is None: + # no comments from reviewer yet. This can happen since + # 'Uploaded patch set X.' is considered a comment. + continue + comments.append(Comment(date, change['_number'], + change['subject'], message)) + + return sorted(comments, key=lambda comment: comment.date, + reverse=True)[0:count] + + +def vote(comment, success, failure, log=False): + for line in comment.message.splitlines(): + if line.startswith("* ") or line.startswith("- "): + job = line.split(' ')[1] + if " : SUCCESS" in line: + success[job] += 1 + if log: + print line + if " : FAILURE" in line: + failure[job] += 1 + if log: + print line + + +def generate_report(name, count, project): + result = {'name': name, 'project': project} + success = collections.defaultdict(int) + failure = collections.defaultdict(int) + + comments = query_gerrit(name, count, project) + + if len(comments) == 0: + print "didn't find anything" + return None + + print "last seen: %s (%s old)" % (comments[0].date, comments[0].age()) + result['last'] = epoch(comments[0].date) + + for comment in comments: + vote(comment, success, failure) + + total = sum(success.values()) + sum(failure.values()) + if total > 0: + success_rate = str(int(sum(success.values()) / + float(total) * 100)) + "%" + result['rate'] = success_rate + print "success rate: %s" % success_rate + return result + + +def print_last_comments(name, count, print_message, project, votes): + success = collections.defaultdict(int) + failure = collections.defaultdict(int) + + comments = query_gerrit(name, count, project) + + message = "last %s comments from '%s'" % (count, name) + if project: + message += " on project '%s'" % project + print message + # sort by time + for i, comment in enumerate(comments): + print "[%d] %s" % (i, comment) + if print_message: + print "message: \"%s\"" % comment.message + print + if votes: + vote(comment, success, failure, log=True) + + if votes: + print "success count by job:" + for job in success.iterkeys(): + print "* %s: %d" % (job, success[job]) + print "failure count by job:" + for job in failure.iterkeys(): + print "* %s: %d" % (job, failure[job]) + + +def epoch(timestamp): + return int(calendar.timegm(timestamp.timetuple())) + + +def main(): + parser = argparse.ArgumentParser(description='list most recent comment by ' + 'reviewer') + parser.add_argument('-n', '--name', + default="Elastic Recheck", + help='unique gerrit name of the reviewer') + parser.add_argument('-c', '--count', + default=10, + type=int, + help='unique gerrit name of the reviewer') + parser.add_argument('-f', '--file', + default=None, + help='yaml file containing list of names to search on' + 'project: name' + ' (overwrites -p and -n)') + parser.add_argument('-m', '--message', + action='store_true', + help='print comment message') + parser.add_argument('-v', '--votes', + action='store_true', + help=('Look in comments for CI Jobs and detect ' + 'SUCCESS/FAILURE')) + parser.add_argument('--json', + nargs='?', + const='lastcomment.json', + help=("Generate report to be stored in the json file " + "specified here. Ignores -v and -m " + "(default: 'lastcomment.json')")) + parser.add_argument('-p', '--project', + help='only list hits for a specific project') + + args = parser.parse_args() + names = {args.project: [args.name]} + if args.file: + with open(args.file) as f: + names = yaml.load(f) + + if args.json: + print "generating report %s" % args.json + print "report is over last %s comments" % args.count + report = {} + timestamp = epoch(datetime.datetime.utcnow()) + report['timestamp'] = timestamp + report['rows'] = [] + + for project in names: + print 'Checking project: %s' % project + for name in names[project]: + print 'Checking name: %s' % name + try: + if args.json: + report['rows'].append(generate_report( + name, args.count, project)) + else: + print_last_comments(name, args.count, args.message, + project, args.votes) + except Exception as e: + print e + pass + + if args.json: + with open(args.json, 'w') as f: + json.dump(report, f) + + +if __name__ == "__main__": + main() diff --git a/monitoring/lastcomment-scoreboard/report.html b/monitoring/lastcomment-scoreboard/report.html new file mode 100644 index 0000000..6f4703a --- /dev/null +++ b/monitoring/lastcomment-scoreboard/report.html @@ -0,0 +1,90 @@ + + +
+Name | +Project | +Last Seen | +Success Rate | +
---|