When developing the monitoring system, it is inevitable to deal with the query of the sequential database, and the time range is an essential condition when checking the sequential database. Therefore, the UI display of the query usually takes the time range as an independent component to let the user interact.

Time ranges are usually presented in two forms: relative time and absolute time. For the monitoring system, relative time is basically used for daily observation of indicators and establishment of kanban, because absolute time can not be updated in time, and it is easy to cause slow queries. The use of absolute time is usually to locate specific problems.

There are two main uses of relative time in our monitoring front end: adhoc queries and kanban. In both cases, relative time serialization is required, the former to share query links, and the latter to save kanban configurations. Here’s how to serialize relative time.

Use key to map

This is the way used in the initial monitoring, which is to save the relative time range through some predefined keys (yesterday, today, Thisweek, etc.), and the front end needs to write extra dead Label Map and Duration Map during display.

const LabelMap = {
  yesterday: 'yesterday'.today: 'today'.thisweek: 'this week'.// and so on..
};

const DurationMap = {
  yesterday: (a)= > [moment().subtract(1.'day').startOf('day'), moment().subtract(1.'day').endOf('day')].today: (a)= > [moment().startOf('day'), moment().endOf('day')].thisweek: (a)= > [moment().startOf('week'), moment().endOf('week')].// and so on..
}
Copy the code

This approach is simple but inflexible. If a new time period is required, the two maps must be changed. And it doesn’t work if the user has some special relative time.

Using structured Data

For the sake of flexibility, we can use objects to hold relative time. Here we need to understand what relative time consists of.

Relative abstraction of time

In the project, the time period we generally use is composed of a start point and an end point, among which a relative time point is generated by a series of calculations, which can be divided into two types: offset and interval beginning and end. The corresponding moment method is

/ / migration
moment().add(1.'hour');
moment().subtract(1.'day');

// start and end of interval
moment().startOf('hour');
moment().endOf('day');
Copy the code

implementation

The corresponding data structure is as follows

type Unit = 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'y';

interface Offset {
  type: 'Offset';
  // Subtract is used to add or subtract
  // op: '+' | '-';
  number: number;
  unit: Unit;
}

interface Period {
  type: 'Period';
  // the startOf or endOf is used to indicate the startOf or endOf
  // op: 'start' | 'end';
  unit: Unit;
}

type Calc = Offset | Period;

interface TimeRange {
  start: Array<Calc>;
  end: Array<Calc>;
}
Copy the code

Simply implement a function that displays Label and a function that evaluates Duration based on the data structure.

Structured data provides great flexibility but exposes several drawbacks:

  1. The function that shows Label is not easy to write, especially for more than two steps of calculation that requires a lot of special judgments, such asLast week,Our data looks like this.[moment().sutract(1, 'w').startOf('w'), moment().sutract(1, 'w').endOf('w')]In turn, formatting the object requires a lot of judgment code.
  2. For ease of use, there is definitely a need for a quick filter, and it takes a lot of code to put this list either on the front end or back end (quick filter below)
  3. Objects are not very convenient to put in Query. For example, there is a function in our monitor Kanban that allows the user to override the default kanban configuration with a time parameter in Query, which would be inconvenient if it were an object.

Use relative time expressions

Wouldn’t the above disadvantages be solved if we could use expressions to represent the above structured data?

Relative time expression

At this point, Grafana had already provided a working prototype, and I rewrote the logic on top of its syntax, adding fault tolerance and syntax features to create a separate library (home page). This expression is based on the structured data implementation in the previous section, but can be more straightforward. For example (from examples)

  • now - 12h: 12 hours ago, same as moment().subtract(12, 'hours')
  • -1d: 1 day ago, same as moment().subtract(1, 'day')
  • now / d: the start of today, same as moment().startOf('day')
  • now \ w: the end of this week, same as moment().endOf('week')
  • now - w / w: the start of last week, same as moment().subtract(1, 'week').startOf('week')

How can structured data be addressed

How to solve the formatting problem

To format an expression, you don’t need to write code to determine the special interval. You just need to map the standard format expression to the corresponding text as in the first method. Such as

const LabelMap = {
  'now-d/d to now-d\\d': 'yesterday'.'now-w/d to now-w\\d': 'Same day last week'.// so on..
}

import { standardize } from 'relative-time-expression';
const start = standardize(' now - 1 d /d'); // return now-d/d
const end = standardize('-d\\d'); // return now-d\d
const label = LabelMap[`${start} to ${end}`] | |`${start} to ${end}`;
expect(label).toEqual('yesterday');
Copy the code

Of course, x hours before we do this, x days before we do this, we still need to write some judgments, just like we did in the last video, as follows

// const start, end = ...

import { parse } from 'relative-time-expression';

if (end === 'now') {
  // omit error catch code
  const ast = parse(start);
  if (ast.body.length === 1 && ast.body[0].type === 'Offset') {
    // If start has only one offset, then it can be formatted as' before {number}{unit} '
    return Before `${ast.body[0].number}${ast.body[0].unit}`;
  }
  // ...
}
Copy the code

Solve the remaining two problems

Both problems are solved once the value becomes a normal string.

Time zone problem

The calculation of the beginning and end of the interval is based on time zone, such as now/ D, and the user usually expects the start time of the day in his or her area (although he or she may want to look up data in another time zone). If the relative time is calculated on the client side, the browser actually sets the correct time zone for us, but on the server side, it only gets the time in the server system’s time zone.

So considering the need for the server to calculate relative time (there is a similar need in monitoring Kanban to call the back-end interface directly with the Kanban component ID), the client needs to bring the time zone information when calling these interfaces. The code for the server side is as follows

import parse from 'rte-moment';
import moment from 'moment-timezone';
const m = parse('now/d', { base: moment().tz(clientTimezone || 'Asia/Shanghai')}); moment().tz('Asia/Shanghai').startOf('day').isSame(m); // true
Copy the code

conclusion

The time component in the monitoring project is basically modeled after Grafana’s time component, which has a lot to learn in terms of monitoring.

Besides typescript, the project was also written by Rust. What impressed me most about Rust is that the whole set of tools for project construction, document generation and dependency management are very easy to use, so you can concentrate on writing code after getting started.


This article turns to my blog