From d09aa16193808d2a231c3d55a8681bd97c08f216 Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Mon, 23 Mar 2020 16:47:04 -0700 Subject: [PATCH] feat: slightly configurable line writer --- ui/src/utils/lineWriter.test.ts | 112 ++++++++++++++++++++++++++++++++ ui/src/utils/lineWriter.ts | 104 +++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 ui/src/utils/lineWriter.test.ts create mode 100644 ui/src/utils/lineWriter.ts diff --git a/ui/src/utils/lineWriter.test.ts b/ui/src/utils/lineWriter.test.ts new file mode 100644 index 00000000000..890b8d8d424 --- /dev/null +++ b/ui/src/utils/lineWriter.test.ts @@ -0,0 +1,112 @@ +import {mocked} from 'ts-jest/utils' + +jest.mock('src/utils/ajax') +import { + createLineFromModel, + buildLineWriter, + Precision, +} from 'src/utils/lineWriter' + +import MockAjax from 'src/utils/ajax' + +describe('creating a line from a model', () => { + it('creates a line without tags', () => { + const measurement = 'performance' + const tags = {} + const fields = {fps: 55} + const timestamp = 1584990314 + + const line = createLineFromModel(measurement, fields, tags, timestamp) + expect(line).toBe('performance fps=55 1584990314') + }) + + it('creates a line when no tags are passed in', () => { + const measurement = 'performance' + const fields = {fps: 55} + + const line = createLineFromModel(measurement, fields) + expect(line).toEqual(expect.stringContaining('performance fps=55')) + }) + + it('creates a line without tags with multiple fields', () => { + const measurement = 'performance' + const tags = {} + const fields = {fps: 49.33333, heap: 48577273} + const timestamp = 1584990314 + + const line = createLineFromModel(measurement, fields, tags, timestamp) + expect(line).toBe('performance fps=49.33333,heap=48577273 1584990314') + }) + + it('creates a line with a tag', () => { + const measurement = 'performance' + const tags = {region: 'us-west'} + const fields = {fps: 49.33333, heap: 48577273} + const timestamp = 1584990314 + + const line = createLineFromModel(measurement, fields, tags, timestamp) + expect(line).toBe( + 'performance,region=us-west fps=49.33333,heap=48577273 1584990314' + ) + }) + + it('creates a line with multiple tags', () => { + const measurement = 'performance' + const tags = {region: 'us-west', status: 'good'} + const fields = {fps: 49.33333, heap: 48577273} + const timestamp = 1584990314 + + const line = createLineFromModel(measurement, fields, tags, timestamp) + expect(line).toBe( + 'performance,region=us-west,status=good fps=49.33333,heap=48577273 1584990314' + ) + }) + + it('alphabetizes tags by key, for write optimization', () => { + const measurement = 'performance' + const tags = {region: 'us-west', environment: 'dev'} + const fields = {fps: 49.33333, heap: 48577273} + const timestamp = 1584990314 + + const line = createLineFromModel(measurement, fields, tags, timestamp) + expect(line).toBe( + 'performance,environment=dev,region=us-west fps=49.33333,heap=48577273 1584990314' + ) + }) +}) + +describe('building a line writer', () => { + const url = 'https://west-west-west.cloud2.influxdata.com' + const orgId = '823723asdfc92134' + const bucketName = 'performance' + const authToken = 'asdf2#%JASjasg==' + + it('builds a line writer with the passed in properties', () => { + const writeLine = buildLineWriter(url, orgId, bucketName, authToken) + + const measurement = 'performance' + const tags = {region: 'us-west', environment: 'dev'} + const fields = {fps: 55.583583242, timeInFrame: 15} + const timestamp = 1585005787 + + writeLine(measurement, fields, tags, Precision.s, timestamp) + const [requestParams, shouldExcludeBasepath] = mocked( + MockAjax + ).mock.calls[0] + + expect(shouldExcludeBasepath).toBeTruthy() + expect(requestParams).toEqual({ + method: 'POST', + url: `${url}/api/v2/write`, + data: createLineFromModel(measurement, fields, tags, timestamp), + headers: { + Authorization: `Token ${authToken}`, + }, + params: { + org: orgId, + bucket: bucketName, + precision: Precision.s, + }, + }) + }) +}) diff --git a/ui/src/utils/lineWriter.ts b/ui/src/utils/lineWriter.ts new file mode 100644 index 00000000000..7924ae6ed9f --- /dev/null +++ b/ui/src/utils/lineWriter.ts @@ -0,0 +1,104 @@ +import AJAX from 'src/utils/ajax' + +export interface Tags { + [key: string]: string | number +} + +export interface Fields { + [key: string]: number | string +} + +export enum Precision { + ns = 'ns', + u = 'u', + ms = 'ms', + s = 's', + m = 'm', + h = 'h', +} + +const nowInSeconds = function nowInSeconds() { + return Math.floor(Date.now() / 1000) +} + +// Build a line writer that can write arbitrary line data +export const createLineFromModel = function createLineFromModel( + measurement: string, + fields: Fields, + tags: Tags = {}, + timestamp: number = nowInSeconds() +): string { + let tagString = '' + Object.keys(tags) + // Sort keys for a little extra perf + // https://v2.docs.influxdata.com/v2.0/write-data/best-practices/optimize-writes/#sort-tags-by-key + .sort() + .forEach((tagKey, i, tagKeys) => { + const tagValue = tags[tagKey] + tagString = `${tagString}${tagKey}=${tagValue}` + + // if this isn't the end of the string, append a comma + if (i < tagKeys.length - 1) { + tagString = `${tagString},` + } + }) + + let fieldString = '' + Object.keys(fields).forEach((fieldKey, i, fieldKeys) => { + const fieldValue = fields[fieldKey] + fieldString = `${fieldString}${fieldKey}=${fieldValue}` + + // if this isn't the end of the string, append a comma + if (i < fieldKeys.length - 1) { + fieldString = `${fieldString},` + } + }) + + let lineStart = measurement + if (tagString !== '') { + lineStart = `${lineStart},${tagString}` + } + + return `${lineStart} ${fieldString} ${timestamp}` +} + +// Builds a basic line writer that will write a line to the url, org and bucket specified. +// Returns a function named `writeLine` that utilizes closures to access the configuration +// arguments passed in to `buildLineWriter`. +export const buildLineWriter = function buildLineWriter( + url: string, + orgId: number | string, + bucketName: string, + authToken: string +) { + return async function writeLine( + measurement: string, + fields: Fields, + tags: Tags = {}, + precision: Precision = Precision.s, + timestamp: number = nowInSeconds() + ) { + const line = createLineFromModel(measurement, fields, tags, timestamp) + + try { + await AJAX( + { + method: 'POST', + url: `${url}/api/v2/write`, + data: line, + headers: { + Authorization: `Token ${authToken}`, + }, + params: { + org: orgId, + bucket: bucketName, + precision, + }, + }, + true + ) + } catch (error) { + console.error(error) + } + } +}