20640: Merge branch 'main' into 20640-computed-permissions-api
[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         #chart {
56           margin-left: -20px;
57         }
58     '''
59
60     def __init__(self, label, summarizers):
61         self.label = label
62         self.summarizers = summarizers
63
64     def html(self, beforechart='', afterchart=''):
65         return '''<!doctype html><html><head>
66         <title>{} stats</title>
67         <script type="text/javascript" src="{}"></script>
68         <script type="text/javascript">{}</script>
69         <style>
70         {}
71         </style>
72         {}
73         </head>
74         <body>
75         <div class="card">
76           <div class="content">
77             <h1>{}</h1>
78           </div>
79         </div>
80         <div class="card">
81           <div class="content" id="tophtml">
82           <h2>Summary</h2>
83           {}
84           </div>
85         </div>
86         <div class="card">
87           <div class="content">
88             <h2>Graph</h2>
89             <div id="chart"></div>
90           </div>
91         </div>
92         <div class="card">
93           <div class="content" id="bottomhtml">
94           <h2>Metrics</h2>
95           {}
96           </div>
97         </div>
98         </body>
99         </html>
100         '''.format(escape(self.label),
101                    self.JSLIB,
102                    self.js(),
103                    self.STYLE,
104                    self.headHTML(),
105                    escape(self.label),
106                    beforechart,
107                    afterchart)
108
109     def js(self):
110         return 'var chartdata = {};\n{}'.format(
111             json.dumps(self.sections()),
112             '\n'.join([pkg_resources.resource_string('crunchstat_summary', jsa).decode('utf-8') for jsa in self.JSASSETS]))
113
114     def sections(self):
115         return [
116             {
117                 'label': s.long_label(),
118                 'charts': [
119                     self.chartdata(s.label, s.tasks, stat)
120                     for stat in (('cpu', ['user+sys__rate', 'user__rate', 'sys__rate']),
121                                  ('mem', ['rss']),
122                                  ('net:eth0', ['tx+rx__rate','rx__rate','tx__rate']),
123                                  ('net:keep0', ['tx+rx__rate','rx__rate','tx__rate']),
124                                  ('statfs', ['used', 'total']),
125                                  )
126                     ],
127             }
128             for s in self.summarizers]
129
130     def chartdata(self, label, tasks, stat):
131         """Return chart data for the given tasks.
132
133         The returned value will be available on the client side as an
134         element of the "chartdata" array.
135         """
136         raise NotImplementedError()
137
138     def headHTML(self):
139         """Return extra HTML text to include in HEAD."""
140         return ''