Skip to content

Commit

Permalink
Bugfix: Dashboard did not pass the Sample parameter correctly. (#3802)
Browse files Browse the repository at this point in the history
This resulted in unsampled data overflowing the 2000 row maximum the GUI
is prepared to accept into the graphs. This means the graphs would
terminate once 2000 rows were reached. Therefore, for a lot of past data
the end of the graph was not very recent.
  • Loading branch information
scudette committed Oct 13, 2024
1 parent a569502 commit f31ca25
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 24 deletions.
44 changes: 32 additions & 12 deletions artifacts/definitions/Server/Monitor/Health.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,50 @@ reports:

template: |
{{ define "CPU" }}
SELECT _ts as Timestamp,
CPUPercent,
int(int=MemoryUse / 1048576) AS MemoryUse_Mb,
TotalFrontends
FROM source(source="Prometheus",
start_time=StartTime, end_time=EndTime,
artifact="Server.Monitor.Health")
LET SampledData <= SELECT * FROM sample(
n=atoi(string=Sample),
query={
SELECT _ts as Timestamp,
CPUPercent,
int(int=MemoryUse / 1048576) AS MemoryUse_Mb,
TotalFrontends
FROM source(source="Prometheus",
start_time=StartTime, end_time=EndTime,
artifact="Server.Monitor.Health")
})
LET Stats <= SELECT count() AS Count,
timestamp(epoch=min(item=Timestamp)) AS MinTime,
timestamp(epoch=max(item=Timestamp)) AS MaxTime,
timestamp(epoch=StartTime) AS StartTime
FROM SampledData
GROUP BY 1
// Include a log for verification. Last data should always be
// very recent and sample should be passed properly.
LET _ <= log(message="Graphs cover times from %v (%v). Actual data available from %v (%v) to %v (%v) with %v rows. Data is sampled every %v samples.", args=[
Stats[0].StartTime.String, humanize(time=Stats[0].StartTime),
Stats[0].MinTime.String, humanize(time=Stats[0].MinTime),
Stats[0].MaxTime.String, humanize(time=Stats[0].MaxTime),
Stats[0].Count, Sample])
SELECT * FROM SampledData
{{ end }}
{{ define "CurrentConnections" }}
SELECT * FROM sample(
SELECT * FROM sample(
n=atoi(string=Sample),
query={
SELECT _ts as Timestamp,
client_comms_current_connections
FROM source(source="Prometheus",
start_time=StartTime, end_time=EndTime,
artifact="Server.Monitor.Health")
})
})
{{ end }}
{{ $time_rows := Query "SELECT timestamp(epoch=now()) AS Now FROM scope()" | Expand }}
{{ $time := Get $time_rows "0.Now" }}
## Server status @ <velo-value value="{{ $time.Format "2006-01-02T15:04:05Z07:00" }}" />
## Server status @ {{ Render ( Get $time_rows "0.Now" ) }}
<p>The following are total across all frontends.</p>
<span class="container">
Expand All @@ -62,7 +82,7 @@ reports:
{{- Query "CurrentConnections" | LineChart "xaxis_mode" "time" "RSS.yaxis" 2 -}}
</span>
</span>
</span>
</span>
## Current Orgs
Expand Down
14 changes: 9 additions & 5 deletions gui/velociraptor/src/components/artifacts/line-charts.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { ReferenceArea, ResponsiveContainer,
Bar, Line, Scatter,
CartesianGrid, XAxis, YAxis, Tooltip } from 'recharts';

import { ToStandardTime } from '../utils/time.jsx';
import { ToStandardTime, FormatRFC3339 } from '../utils/time.jsx';
import UserConfig from '../core/user.jsx';

const strokes = [
"#ff7300", "#f48f8f", "#207300", "#f4208f"
Expand All @@ -20,15 +21,17 @@ const shapes = [
];

class CustomTooltip extends React.Component {
static propTypes = {
static contextType = UserConfig;

static propTypes = {
type: PropTypes.string,
payload: PropTypes.array,
columns: PropTypes.array,
data: PropTypes.array,
active: PropTypes.any,
}
}

render() {
render() {
const { active } = this.props;
if (active) {
const { payload } = this.props;
Expand All @@ -51,7 +54,8 @@ class CustomTooltip extends React.Component {
if (_.isNaN(now)) {
value = "";
} else if(_.isDate(now)) {
value = now.toISOString();
let timezone = this.context.traits.timezone || "UTC";
value = FormatRFC3339(now, timezone);
}

return (
Expand Down
8 changes: 6 additions & 2 deletions gui/velociraptor/src/components/hunts/new-hunt.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -471,8 +471,12 @@ export default class NewHuntWizard extends React.Component {

let hunt_parameters = this.state.hunt_parameters;
if (hunt_parameters.expires) {
// hunt_parameters.expires is already an RFC3339 string.
result.expires = hunt_parameters.expires;
// hunt_parameters.expires is an RFC3339 string but we
// expect microseconds here.
let ts = ToStandardTime(hunt_parameters.expires);
if (_.isDate(ts)) {
result.expires = ts.getTime() * 1000;
}
}

if (hunt_parameters.include_condition === "labels") {
Expand Down
25 changes: 20 additions & 5 deletions gui/velociraptor/src/components/sidebar/user-dashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,24 @@ import ToolTip from '../widgets/tooltip.jsx';
import { withRouter } from "react-router-dom";

const ranges = [
{desc: "Last Hour", sec: 60*60, sample: 1, rows: 400},
{desc: "Last Day", sec: 60*60*24, sample: 6, rows: 2000},
{desc: "Last 2 days", sec: 60*60*24*2, sample: 10, rows: 2000},
{desc: "Last Week", sec: 60*60*24*7, sample: 40, rows: 2000},
// Samples are taken every 10 sec by default (6 samples per
// minute). Therefore the rows limit should be a fallback to
// prevent really huge data sets.

// Expected 6 * 60 = 360
{desc: T("Last Hour"), sec: 60*60, sample: 1, rows: 400},

// Expected 6 * 60 * 6 / 2 = 1080
{desc: T("Last 6 Hours"), sec: 60*60*6, sample: 2, rows: 2000},

// Expected 6 * 60 * 24 / 6 = 1440
{desc: T("Last Day"), sec: 60*60*24, sample: 6, rows: 2000},

// Expected 6 * 60 * 24 * 2 / 10 = 1728
{desc: T("Last 2 days"), sec: 60*60*24*2, sample: 10, rows: 2000},

// Expected 6 * 60 * 24 * 7 / 40 = 1512
{desc: T("Last Week"), sec: 60*60*24*7, sample: 40, rows: 2000},
];


Expand Down Expand Up @@ -109,7 +123,8 @@ class UserDashboard extends React.Component {
type="SERVER_EVENT"
params={{start_time: this.state.start_time,
version: this.state.version,
sample: this.state.sample}}
parameters: [{name: "Sample",
"default": this.state.sample.toString()}]}}
/>
</div>
</>
Expand Down
13 changes: 13 additions & 0 deletions reporting/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,18 @@ func (self *GuiTemplateEngine) queryRows(queries ...string) []*ordereddict.Dict
return result
}

// Render the special GUI markup for given value.
func (self *GuiTemplateEngine) renderFunction(a interface{}, opts ...interface{}) interface{} {
switch t := a.(type) {
case time.Time:
res := fmt.Sprintf(`<velo-value value="%v"></velo-value>`,
t.Format(time.RFC3339))
return res
}

return a
}

func (self *GuiTemplateEngine) Error(fmt_str string, argv ...interface{}) string {
self.Scope.Log(fmt_str, argv...)
return ""
Expand Down Expand Up @@ -615,6 +627,7 @@ func NewGuiTemplateEngine(
"TimeChart": template_engine.TimeChart,
"Timeline": template_engine.Timeline,
"Get": template_engine.getFunction,
"Render": template_engine.renderFunction,
"Expand": template_engine.Expand,
"import": template_engine.Import,
"str": strval,
Expand Down

0 comments on commit f31ca25

Please sign in to comment.