UNPKG

cloki

Version:

LogQL API with Clickhouse Backend

388 lines (366 loc) 9.63 kB
const { hashLabels, parseLabels } = require('../../common') const { getPlg } = require('../../plugins/engine') const Sql = require('@cloki/clickhouse-sql') /** * @param query {registry_types.Request | string[]} * @param clauses {string[]} * @returns {registry_types.Request | string[]} */ module.exports._and = (query, clauses) => { if (Array.isArray(query)) { if (!query.length) { return ['AND', ...clauses] } return query[0] === 'AND' ? [...query, ...clauses] : ['AND', query, ...clauses] } query = { ...query } if (!query.where) { query.where = ['AND'] } else if (query.where[0] !== 'AND') { query.where = ['AND', query.where] } else { query.where = [...query.where] } query.where.push.apply(query.where, clauses) return query } /** * * @param query {Select} * @returns {DataStream[]} */ module.exports.getStream = (query) => { return query && query.ctx && query.ctx.stream ? query.ctx.stream : [] } /** * * @param query {Select} * @returns {boolean} */ module.exports.hasStream = (query) => { return module.exports.getStream(query).length > 0 } /** * * @param query {Select} * @param stream {function(DataStream): DataStream} * @returns {Select} */ module.exports.addStream = (query, stream) => { if (!query) { throw new Error('query is undefined') } if (query && query.ctx && query.ctx.stream) { query.ctx.stream.push(stream) return query } if (query && query.ctx) { query.ctx.stream = [stream] return query } query.ctx = { stream: [stream] } return query } /** * @param query {registry_types.Request} * @returns {registry_types.Request} */ module.exports.querySelectorPostProcess = (query) => { return query } /** * * @param token {Token} * @returns {string} */ module.exports.unquoteToken = (token) => { let value = token.Child('quoted_str').value value = `"${value.substr(1, value.length - 2)}"` return JSON.parse(value) } /** * * @param s {DataStream} * @param fn * @returns {DataStream} */ module.exports.map = (s, fn) => s.map((e) => { return new Promise((resolve) => { setImmediate(() => { resolve(fn(e)) }) }) }) /** * * @param token {Token} * @returns {number} */ module.exports.getDuration = (token) => { return module.exports.durationToMs(token.Child('duration_value').value) // Math.max(duration, query.ctx && query.ctx.step ? query.ctx.step : 1000); } const getDuration = module.exports.getDuration /** * * @param eof {any} * @returns boolean */ module.exports.isEOF = (eof) => eof.EOF /** * * @param type {string} * @param cb {(function(any): any) | undefined} * @returns {Object<string, (function(any): any)>} */ module.exports.getPlugins = (type, cb) => { const _plgs = getPlg({ type: type }) const plgs = {} for (const _e of Object.values(_plgs)) { for (const e of Object.entries(_e)) { plgs[e[0]] = cb ? cb(e[1]) : () => e[1] } } return plgs /* for (let file of glob.sync(path + "/*.js")) { const mod = require(file); for (let fn of Object.keys(mod)) { plugins[fn] = cb ? cb(mod[fn]()) : mod[fn](); } } return plugins; */ } /** * * @param query {Select} * @returns {boolean} */ module.exports.hasExtraLabels = (query) => { return query.select().some(f => f[1] === 'extra_labels') } /** * * @param query {Select} * @returns {SQLObject} */ module.exports.concatLabels = (query) => { if (module.exports.hasExtraLabels(query)) { return new Sql.Raw('arraySort(arrayConcat(arrayFilter(x -> arrayExists(y -> y.1 == x.1, extra_labels) == 0, labels), extra_labels))') } return new Sql.Raw('labels') } /** * sum_over_time(unwrapped-range): the sum of all values in the specified interval. * @param token {Token} * @param query {Select} * @param byWithoutName {string} name of the by_without token * @returns {Select} */ function applyByWithoutStream (token, query, byWithoutName) { const isBy = token.Child(byWithoutName).value === 'by' const filterLabels = token.Children('label').map(l => l.value) return module.exports.addStream(query, /** * * @param stream {DataStream} */ (stream) => stream.map(e => { if (!e || !e.labels) { return e } const labels = [...Object.entries(e.labels)].filter(l => (isBy && filterLabels.includes(l[0])) || (!isBy && !filterLabels.includes(l[0])) ) return { ...e, labels: parseLabels(labels) } })) } /** * * @param values {Object} * @param timestamp {number} * @param value {number} * @param duration {number} * @param step {number} * @param counterFn {function(any, any, number): any} * @returns {Object} */ function addTimestamp (values, timestamp, value, duration, step, counterFn) { const timestampWithoutStep = Math.floor(timestamp / duration) * duration const timestampWithStep = step > duration ? Math.floor(timestampWithoutStep / step) * step : timestampWithoutStep if (!values) { values = {} } if (!values[timestampWithStep]) { values[timestampWithStep] = {} } if (!values[timestampWithStep][timestampWithoutStep]) { values[timestampWithStep][timestampWithoutStep] = 0 } values[timestampWithStep][timestampWithoutStep] = counterFn(values[timestampWithStep][timestampWithoutStep], value, timestamp) return values } /** * * @param query {Select} * @returns {boolean} */ module.exports.hasExtraLabels = (query) => { return query.select().some((x) => x[1] === 'extra_labels') } module.exports.timeShiftViaStream = (token, query) => { let tsMoveParam = null if (!query.params.timestamp_shift) { tsMoveParam = new Sql.Parameter('timestamp_shift') query.addParam(tsMoveParam) } else { tsMoveParam = query.params.timestamp_shift } const duration = module.exports.getDuration(token) /** * @param s {DataStream} */ const stream = (s) => s.map((e) => { if (tsMoveParam.get()) { e.timestamp_ns -= (parseInt(tsMoveParam.get()) % duration) } return e }) return module.exports.addStream(query, stream) } /** * * @param token {Token} * @param query {Select} * @param counterFn {function(any, any, number): any} * @param summarizeFn {function(any): number} * @param lastValue {boolean} if the applier should take the latest value in step (if step > duration) * @param byWithoutName {string} name of the by_without token * @returns {Select} */ module.exports.applyViaStream = (token, query, counterFn, summarizeFn, lastValue, byWithoutName) => { query.ctx.matrix = true byWithoutName = byWithoutName || 'by_without' if (token.Child(byWithoutName)) { query = applyByWithoutStream(token.Child(`opt_${byWithoutName}`), query, byWithoutName) } let results = new Map() const duration = getDuration(token, query) query.ctx.duration = duration const step = query.ctx.step /** * @param s {DataStream} */ const stream = (s) => s.remap((emit, e) => { if (!e || !e.labels) { for (const v of results.values()) { const ts = [...Object.entries(v.values)] ts.sort() for (const _v of ts) { let value = Object.entries(_v[1]) value.sort() value = lastValue ? value[value.length - 1][1] : value[0][1] value = summarizeFn(value)// Object.values(_v[1]).reduce((sum, v) => sum + summarizeFn(v), 0); emit({ labels: v.labels, timestamp_ns: _v[0], value: value }) } } results = new Map() emit({ EOF: true }) return } const l = hashLabels(e.labels) if (!results.has(l)) { results.set(l, { labels: e.labels, values: addTimestamp(undefined, e.timestamp_ns, e, duration, step, counterFn) }) } else { results.get(l).values = addTimestamp( results.get(l).values, e.timestamp_ns, e, duration, step, counterFn ) } }) return module.exports.addStream(query, stream) } /** * * @param str {string} * @param custom {(function(string): string | undefined) | undefined} * @param customSlash {(function(string): (string | undefined)) | undefined} * @return {string} */ module.exports.unquote = (str, custom, customSlash) => { const quote = str.substr(0, 1) switch (quote) { case '"': case '`': break default: throw new Error(`Unknown quote: ${quote}`) } str = str.trim() str = str.substr(1, str.length - 2) let res = '' let slash = false for (let i = 0; i < str.length; i++) { if (!slash) { if (custom && custom(str[i])) { res += custom(str[i]) continue } if (str[i] === quote) { throw new Error('Unexpected quote') } switch (str[i]) { case '\\': slash = true continue default: res += str[i] } } if (slash) { slash = false if (customSlash && customSlash(str[i])) { res += customSlash(str[i]) continue } if (str[i] === quote) { res += quote continue } switch (str[i]) { case 'r': res += '\r' break case 'n': res += '\n' break case 't': res += '\t' break default: res += '\\' + str[i] } } } return res } module.exports.sharedParamNames = { samplesTable: 'samplesTable', timeSeriesTable: 'timeSeriesTable', from: 'from', to: 'to', limit: 'limit' } /** * * @param durationStr {string} * @returns {number} */ module.exports.durationToMs = require('../../common').durationToMs