1
#include "source/server/admin/stats_html_render.h"
2

            
3
#include <algorithm>
4

            
5
#include "source/common/buffer/buffer_impl.h"
6
#include "source/common/common/assert.h"
7
#include "source/common/filesystem/filesystem_impl.h"
8
#include "source/common/html/utility.h"
9
#include "source/server/admin/admin_html_util.h"
10

            
11
#include "absl/strings/str_replace.h"
12

            
13
// Note: if you change this file, it's advisable to manually run
14
// test/integration/admin_html/web_test.sh to semi-automatically validate
15
// the web interface, in addition to updating and running unit tests.
16
//
17
// The admin web test does not yet run automatically.
18

            
19
namespace Envoy {
20
namespace Server {
21

            
22
StatsHtmlRender::StatsHtmlRender(Http::ResponseHeaderMap& response_headers,
23
                                 Buffer::Instance& response, const StatsParams& params)
24
8
    : StatsTextRender(params), active_(params.format_ == StatsFormat::ActiveHtml),
25
8
      json_histograms_(!active_ &&
26
8
                       params.histogram_buckets_mode_ == Utility::HistogramBucketsMode::Detailed) {
27
8
  AdminHtmlUtil::renderHead(response_headers, response);
28
8
}
29

            
30
7
void StatsHtmlRender::finalize(Buffer::Instance& response) {
31
7
  if (first_histogram_) {
32
6
    response.add("</pre>\n");
33
6
  }
34

            
35
7
  AdminHtmlUtil::finalize(response);
36
7
}
37

            
38
void StatsHtmlRender::setupStatsPage(const Admin::UrlHandler& url_handler,
39
4
                                     const StatsParams& params, Buffer::Instance& response) {
40
4
  AdminHtmlUtil::renderTableBegin(response);
41
4
  AdminHtmlUtil::renderEndpointTableRow(response, url_handler, params.query_, 1, !active_, active_);
42
4
  if (active_) {
43
1
    std::string buf;
44
1
    response.add(AdminHtmlUtil::getResource("active_params.html", buf));
45
1
  }
46
4
  AdminHtmlUtil::renderTableEnd(response);
47
4
  std::string buf;
48
4
  if (active_) {
49
1
    std::string buf2;
50
1
    response.addFragments({"<script>\n", AdminHtmlUtil::getResource("histograms.js", buf),
51
1
                           AdminHtmlUtil::getResource("active_stats.js", buf2), "</script>\n"});
52
4
  } else {
53
3
    response.addFragments(
54
3
        {"<script>\n", AdminHtmlUtil::getResource("histograms.js", buf), "</script>\n<pre>\n"});
55
3
  }
56
4
}
57

            
58
void StatsHtmlRender::generate(Buffer::Instance& response, const std::string& name,
59
3
                               const std::string& value) {
60
3
  ASSERT(first_histogram_);
61
3
  response.addFragments({name, ": \"", Html::Utility::sanitize(value), "\"\n"});
62
3
}
63

            
64
2
void StatsHtmlRender::noStats(Buffer::Instance& response, absl::string_view types) {
65
2
  ASSERT(first_histogram_);
66
2
  if (!active_) {
67
2
    response.addFragments({"</pre>\n<br/><i>No ", types, " found</i><br/>\n<pre>\n"});
68
2
  }
69
2
}
70

            
71
// When using Detailed mode, we override the generate method for HTML to trigger
72
// some JS that will render the histogram graphically. We will render that from
73
// JavaScript and convey the histogram data to the JS via JSON, so we can
74
// delegate to an instantiated JSON `sub-renderer` that will write into
75
// json_data_. that `sub_renderer` will only be populated in Detailed mode.
76
//
77
// All other modes default to rendering the histogram textually.
78
void StatsHtmlRender::generate(Buffer::Instance& response, const std::string& name,
79
4
                               const Stats::ParentHistogram& histogram) {
80
4
  if (json_histograms_) {
81
2
    Json::BufferStreamer streamer(response);
82

            
83
    // If this is the first histogram we are rendering, then we need to first
84
    // generate the supported-percentiles array sand save it in a constant.
85
    //
86
    // We use a separate <script> tag for each histogram so that the browser can
87
    // begin parsing each potentially large histogram as it is generated,rather
88
    // than building up a huge json structure with all the histograms and
89
    // blocking rendering until that is parsed.
90
2
    if (first_histogram_) {
91
1
      first_histogram_ = false;
92
1
      response.add("</pre>\n<div id='histograms'></div>\n<script>\nconst supportedPercentiles = ");
93
1
      { StatsJsonRender::populateSupportedPercentiles(*streamer.makeRootArray()); }
94
1
      response.add(";\nconst histogramDiv = document.getElementById('histograms');\n");
95
      // The first histogram will share the first script tag with the histogram
96
      // div and supportedPercentiles array constants.
97
1
    } else {
98
1
      response.add("<script>\n");
99
1
    }
100
2
    response.add("renderHistogram(histogramDiv, supportedPercentiles,\n");
101
2
    { StatsJsonRender::generateHistogramDetail(name, histogram, *streamer.makeRootMap()); }
102
2
    response.add(");\n</script>\n");
103
2
  } else {
104
2
    StatsTextRender::generate(response, name, histogram);
105
2
  }
106
4
}
107

            
108
} // namespace Server
109
} // namespace Envoy