21121: Produce limited report if prometheus is not available
[arvados.git] / tools / crunchstat-summary / crunchstat_summary / webchart.py
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 try:
6     from html import escape
7 except ImportError:
8     from cgi import escape
9
10 import json
11 from typing import ItemsView
12 import pkg_resources
13
14
15 class WebChart(object):
16     """Base class for a web chart.
17
18     Subclasses must assign JSLIB and JSASSETS, and override the
19     chartdata() method.
20     """
21     JSLIB = None
22     JSASSETS = None
23
24     STYLE = '''
25         body {
26           background: #fafafa;
27           font-family: "Roboto", "Helvetica", "Arial", sans-serif;
28           font-size: 0.875rem;
29           color: rgba(0, 0, 0, 0.87);
30           font-weight: 400;
31         }
32         .card {
33           background: #ffffff;
34           box-shadow: 0px 1px 5px 0px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 3px 1px -2px rgba(0,0,0,0.12);
35           border-radius: 4px;
36           margin: 20px;
37         }
38         .content {
39           padding: 2px 16px 8px 16px;
40         }
41         table {
42           border-spacing: 0px;
43         }
44         tr {
45           height: 36px;
46           text-align: left;
47         }
48         th {
49           padding-right: 4em;
50           border-top: 1px solid rgba(224, 224, 224, 1);
51         }
52         td {
53           padding-right: 2em;
54           border-top: 1px solid rgba(224, 224, 224, 1);
55         }
56         #chart {
57           margin-left: -20px;
58         }
59     '''
60
61     def __init__(self, label, summarizers):
62         self.label = label
63         self.summarizers = summarizers
64
65     def cardlist(self, items):
66         if not isinstance(items, list):
67             items = [items]
68
69         return "\n".join(
70                 """<div class="card">
71           <div class="content">
72           {}
73           </div>
74         </div>""".format(i) for i in items)
75
76     def html(self, beforechart='', afterchart=''):
77         graph_card = ""
78
79         if self.summarizers:
80             graph_card = """
81             <div class="card">
82               <div class="content">
83                 <h2>Graph</h2>
84                 <div id="chart"></div>
85               </div>
86             </div>
87             """
88
89         return '''<!doctype html><html><head>
90         <title>{label} stats</title>
91         <script type="text/javascript" src="{jslib}"></script>
92         <script type="text/javascript">{ourjs}</script>
93         <style>
94         {style}
95         </style>
96         {header}
97         </head>
98         <body>
99         <div class="card">
100           <div class="content">
101             <h1>{label}</h1>
102           </div>
103         </div>
104         {beforegraph}
105         {graph}
106         {aftergraph}
107         </body>
108         </html>
109         '''.format(label=escape(self.label),
110                    jslib=self.JSLIB,
111                    ourjs=self.js(),
112                    style=self.STYLE,
113                    header=self.headHTML(),
114                    beforegraph=self.cardlist(beforechart),
115                    graph=graph_card,
116                    aftergraph=self.cardlist(afterchart))
117
118     def js(self):
119         return 'var chartdata = {};\n{}'.format(
120             json.dumps(self.sections()),
121             '\n'.join([pkg_resources.resource_string('crunchstat_summary', jsa).decode('utf-8') for jsa in self.JSASSETS]))
122
123     def sections(self):
124         return [
125             {
126                 'label': s.long_label(),
127                 'charts': [
128                     self.chartdata(s.label, s.tasks, stat)
129                     for stat in (('cpu', ['user+sys__rate', 'user__rate', 'sys__rate']),
130                                  ('mem', ['rss']),
131                                  ('net:eth0', ['tx+rx__rate','rx__rate','tx__rate']),
132                                  ('net:keep0', ['tx+rx__rate','rx__rate','tx__rate']),
133                                  ('statfs', ['used', 'total']),
134                                  )
135                     ],
136             }
137             for s in self.summarizers]
138
139     def chartdata(self, label, tasks, stat):
140         """Return chart data for the given tasks.
141
142         The returned value will be available on the client side as an
143         element of the "chartdata" array.
144         """
145         raise NotImplementedError()
146
147     def headHTML(self):
148         """Return extra HTML text to include in HEAD."""
149         return ''