19744: Apply styling to roughly match workbench
[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 import pkg_resources
12
13
14 class WebChart(object):
15     """Base class for a web chart.
16
17     Subclasses must assign JSLIB and JSASSETS, and override the
18     chartdata() method.
19     """
20     JSLIB = None
21     JSASSET = None
22
23     STYLE = '''
24         body {
25           background: #fafafa;
26           font-family: "Roboto", "Helvetica", "Arial", sans-serif;
27           font-size: 0.875rem;
28           color: rgba(0, 0, 0, 0.87);
29           font-weight: 400;
30         }
31         .card {
32           background: #ffffff;
33           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);
34           border-radius: 4px;
35           margin: 20px;
36         }
37         .content {
38           padding: 2px 16px 8px 16px;
39         }
40         table {
41           border-spacing: 0px;
42         }
43         tr {
44           height: 36px;
45           text-align: left;
46         }
47         th {
48           padding-right: 4em;
49           border-top: 1px solid rgba(224, 224, 224, 1);
50         }
51         td {
52           padding-right: 2em;
53           border-top: 1px solid rgba(224, 224, 224, 1);
54         }
55     '''
56
57     def __init__(self, label, summarizers):
58         self.label = label
59         self.summarizers = summarizers
60
61     def html(self, beforechart='', afterchart=''):
62         return '''<!doctype html><html><head>
63         <title>{} stats</title>
64         <script type="text/javascript" src="{}"></script>
65         <script type="text/javascript">{}</script>
66         <style>
67         {}
68         </style>
69         {}
70         </head>
71         <body>
72         <div class="card">
73           <div class="content">
74             <h1>{}</h1>
75           </div>
76         </div>
77         <div class="card">
78           <div class="content" id="tophtml">
79           <h2>Summary</h2>
80           {}
81           </div>
82         </div>
83         <div class="card">
84           <div class="content">
85             <h2>Graph</h2>
86             <div id="chart"></div>
87           </div>
88         </div>
89         <div class="card">
90           <div class="content" id="bottomhtml">
91           <h2>Metrics</h2>
92           {}
93           </div>
94         </div>
95         </body>
96         </html>
97         '''.format(escape(self.label),
98                    self.JSLIB,
99                    self.js(),
100                    self.STYLE,
101                    self.headHTML(),
102                    escape(self.label),
103                    beforechart,
104                    afterchart)
105
106     def js(self):
107         return 'var chartdata = {};\n{}'.format(
108             json.dumps(self.sections()),
109             '\n'.join([pkg_resources.resource_string('crunchstat_summary', jsa).decode('utf-8') for jsa in self.JSASSETS]))
110
111     def sections(self):
112         return [
113             {
114                 'label': s.long_label(),
115                 'charts': [
116                     self.chartdata(s.label, s.tasks, stat)
117                     for stat in (('cpu', ['user+sys__rate', 'user__rate', 'sys__rate']),
118                                  ('mem', ['rss']),
119                                  ('net:eth0', ['tx+rx__rate','rx__rate','tx__rate']),
120                                  ('net:keep0', ['tx+rx__rate','rx__rate','tx__rate']),
121                                  ('statfs', ['used', 'total']),
122                                  )
123                     ],
124             }
125             for s in self.summarizers]
126
127     def chartdata(self, label, tasks, stat):
128         """Return chart data for the given tasks.
129
130         The returned value will be available on the client side as an
131         element of the "chartdata" array.
132         """
133         raise NotImplementedError()
134
135     def headHTML(self):
136         """Return extra HTML text to include in HEAD."""
137         return ''