Skip to main content

Events visualization

In this guide, you'll learn how to visualize multiple metrics and add annotations to the chart based on host events.

App Screenshot App Screenshot

The Annotations component

In the Annotations component, we'll visualize the CPU and memory used by a specific host. On top of that, we'll add chart annotations with Davis events from the same host. We'll use DQL to fetch the CPU, memory, and Davis events data via the useDqlQuery hook. Finally, we'll use the TimeseriesChart component for the visualization.

The DQL queries for CPU and memory usage use the DQL timeseries command. After we have the results, we need to convert them to the appropriate type for the TimeseriesChart using the convertQueryResultToTimeseries function. Additionally, we'll merge both converted outputs to have one time series for each metric.

For the Davis events, the DQL query uses the record type events and filters by event.kind, so we only get what interests us. Similarly to the earlier queries, we need to convert the results to the appropriate type for the TimeseriesChart.Annotations component.

src/app/components/Annotations.tsx
import React from 'react';
import {
Annotation,
convertQueryResultToTimeseries,
Flex,
ProgressCircle,
Surface,
TimeseriesAnnotations,
TimeseriesChart,
} from '@dynatrace/strato-components-preview';
import { Colors } from '@dynatrace/strato-design-tokens';
import { QueryResult, ResultRecord } from '@dynatrace-sdk/client-query';
import { useDqlQuery } from '@dynatrace-sdk/react-hooks';

type EventAnnotation = Annotation & {
eventId: string;
color: string;
};

const hostId = 'HOST-3873B1D6B9280D7C';
const from = 'now() - 3d';

const EVENTS_QUERY = `fetch events, from: ${from}
| filter event.kind == "DAVIS_EVENT"
| filter dt.entity.host == "${hostId}"
| sort timestamp desc
| summarize {
event.name = takeFirst(event.name),
event.description = takeFirst(event.description),
event.start = takeFirst(event.start),
event.end = takeFirst(event.end),
event.status = takeFirst(event.status)
}, by:{event.id}`;
const HOST_CPU_QUERY = `timeseries from: ${from}, \`CPU Usage\` = avg(dt.host.cpu.usage),
filter:dt.entity.host == "${hostId}"`;
const HOST_RAM_QUERY = `timeseries from: ${from}, \`RAM Usage \` = avg(dt.host.memory.usage),
filter:dt.entity.host == "${hostId}"`;

const buildAnnotations = (data: QueryResult): EventAnnotation[] =>
data.records
.filter((record) => record !== null)
.map((event: ResultRecord) => {
const start = new Date(Number(event['event.start'] as string) / 1000 / 1000);
const end = event['event.end'] ? new Date(Number(event['event.end'] as string) / 1000 / 1000) : new Date();
const color =
event['event.status'] === 'ACTIVE'
? Colors.Charts.Loglevel.Warning.Default
: Colors.Charts.Loglevel.Debug.Default;

return {
start,
end,
color,
title: event['event.name'] as string,
description: event['event.description'] as string,
eventId: event['event.id'] as string,
};
});

const buildTimeseries = (...queryResults: (QueryResult | undefined)[]) =>
queryResults.flatMap((res) => (res ? convertQueryResultToTimeseries(res) : []));

export const Annotations = () => {
const hostCpuSeries = useDqlQuery({
body: { query: HOST_CPU_QUERY },
enrich: 'metric-metadata',
});
const hostRamSeries = useDqlQuery({
body: { query: HOST_RAM_QUERY },
enrich: 'metric-metadata',
});
const events = useDqlQuery({ body: { query: EVENTS_QUERY } });
const isLoading = hostCpuSeries.isLoading || hostRamSeries.isLoading || events.isLoading;

return (
<Flex flexDirection="column" padding={32}>
<Surface>
{isLoading && <ProgressCircle />}
{hostCpuSeries.data && hostRamSeries.data && (
<TimeseriesChart data={buildTimeseries(hostCpuSeries.data, hostRamSeries.data)}>
{events.data && events.data.records.length > 0 && (
<TimeseriesChart.Annotations>
<TimeseriesAnnotations.Track>
{buildAnnotations(events.data).map((event) => (
<TimeseriesAnnotations.Marker color={event.color} key={event.eventId} data={event} />
))}
</TimeseriesAnnotations.Track>
</TimeseriesChart.Annotations>
)}
</TimeseriesChart>
)}
</Surface>
</Flex>
);
};
Tip

This operation requires the following scopes:

  • storage:buckets:read
  • storage:events:read
  • storage:metrics:read
Read more about scopes in this guide.

Summary

With these few lines of code, we've created a useful visualization in very little time. We hope you found this guide helpful and got the inspiration to build more Dynatrace Apps.

Still have questions?
Find answers in the Dynatrace Community