From 03d582d1e19a0c843746a88af26023db95bc20d3 Mon Sep 17 00:00:00 2001 From: Everett Ross Date: Fri, 3 May 2019 17:22:00 -0400 Subject: [PATCH 01/13] WIP - Initial parsing function and tests Signed-off-by: Everett Ross --- .../DeepDependencyGraph/parse-payload.test.js | 69 ++++++++++ .../parse-payload.test.resource.js | 50 ++++++++ .../DeepDependencyGraph/parse-payload.tsx | 119 ++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js create mode 100644 packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resource.js create mode 100644 packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js new file mode 100644 index 0000000000..6f263b986e --- /dev/null +++ b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js @@ -0,0 +1,69 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// const fs = require('fs'); + +import _filter from 'lodash/filter'; +import _flatten from 'lodash/flatten'; +import _map from 'lodash/map'; + +import parsePayload from './parse-payload'; +import * as testResource from './parse-payload.test.resource'; + +describe('parse payload', () => { + + function parsedOutputValidator(payload, focalIndices) { + const { paths, services } = parsePayload(payload, testResource.focalNode); + const serviceNames = Object.keys(services); + let membersProcessed = 0; + expect(serviceNames).toEqual(Array.from(new Set(_map(_flatten(payload), 'service')))); + serviceNames.forEach(serviceName => { + expect(Object.keys(services[serviceName].operations)).toEqual(Array.from(new Set(_map(_filter(_flatten(payload), { service: serviceName}), 'operation')))); + }); + + paths.forEach((path, pathResultIndex) => { + expect(path.focalIdx).toBe(focalIndices[pathResultIndex]); + path.members.forEach((member, memberResultIndex) => { + const { distance, memberOf, operation, pathIdx, visibilityIdx } = member; + expect(distance).toBe(pathIdx - focalIndices[pathResultIndex]); + expect(memberOf).toBe(path); + expect(operation.name).toBe(payload[pathResultIndex][memberResultIndex].operation); + expect(operation.pathElems.includes(member)).toBe(true); + expect(operation.service.name).toBe(payload[pathResultIndex][memberResultIndex].service); + expect(pathIdx).toBe(memberResultIndex); + expect(visibilityIdx).toBe(membersProcessed++); + }); + }); + } + + it('parses an extremely simple payload', () => { + const { simplePath } = testResource; + parsedOutputValidator([simplePath], [2]); + }); + + it('parses a path with multiple operations per service and multiple services per operation', () => { + const { longSimplePath } = testResource; + parsedOutputValidator([longSimplePath], [6]); + }); + + it('parses a payload with significant overlap between paths', () => { + const { simplePath, longSimplePath } = testResource; + parsedOutputValidator([simplePath, longSimplePath], [2, 6]); + }); + + it('parses a path that contains the focal node twice', () => { + const { doubleFocalPath } = testResource; + parsedOutputValidator([doubleFocalPath], [2]); + }); +}); diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resource.js b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resource.js new file mode 100644 index 0000000000..846cd3c6ed --- /dev/null +++ b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resource.js @@ -0,0 +1,50 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +const simpleNodeMaker = label => ({ + operation: `${label}Operation`, + service: `${label}service`, +}); + +export const focalNode = simpleNodeMaker('focal'); + +const pathLengthener = path => { + const prequels = []; + const sequels = []; + path.forEach(({ operation, service }) => { + if (operation !== focalNode.operation && service !== focalNode.service) { + prequels.push({ + operation: `prequel-${operation}`, + service, + }); + sequels.push({ + operation, + service: `sequel-${service}`, + }); + } + }); + return [...prequels, ...path, ...sequels]; +} + +const firstNode = simpleNodeMaker('first'); +const beforeNode = simpleNodeMaker('before'); +const afterNode = simpleNodeMaker('after'); +const lastNode = simpleNodeMaker('last'); + +export const simplePath = [firstNode, beforeNode, focalNode, afterNode, lastNode]; +export const longSimplePath = pathLengthener([firstNode, beforeNode, focalNode, afterNode, lastNode]); + +const midNode = simpleNodeMaker('mid'); + +export const doubleFocalPath = [firstNode, beforeNode, focalNode, midNode, focalNode, afterNode, lastNode]; diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx new file mode 100644 index 0000000000..5049276d20 --- /dev/null +++ b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx @@ -0,0 +1,119 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +type TPayload = { + operation: string; + service: string; +}[][]; + +type TService = { // de-duped + name: string; + operations: Record; + /* static service-based data added here later, e.g.: tier, PD link, uOwn link */ +} + +type TOperation = { // de-duped + name: string; + pathElems: TPathElem[]; + service: TService; + /* static operation-based data added here later, e.g.: SLA(?) */ +} + +type TPathElem = { // super-duped + memberOf: TPath; + operation: TOperation; + pathIdx: number; // in conjunction with focalIdx on TPath this can be used to calculate distance, isFocal, up/down stream + visibilityIdx: number; + /* dynamic, path-based, node data added here later, e.g.: contextual error rate */ +} + + +type TPath = { // One TPath per payload path + focalIdx: number; // Index of focal node in this path, helps with hops management + members: TPathElem[]; + /* dynamic, path data added here later, e.g.: QPS */ +} + +/* +type TGraphEdge: { // Derived data + pathEdges: TPathEdge[]; +} + */ + +type TOperationMap = Record; +type TServiceMap = Record; + +type TParsedPayload = { + // operations: TOperationMap; + paths: TPath[]; + services: TServiceMap; +} + +export default function parsePayload(payload: TPayload, focalNode: { service: string, operation?: string }): TParsedPayload { + // const operationMap: TOperationMap = {}; + const serviceMap: TServiceMap = {}; + let visibilityIdx = 0; + + const paths = payload.map(payloadPath => { + // path with stand-in values in order to have obj to which to assign memberOf for each pathElem. + const path: TPath = { + focalIdx: -1, + members: [], + }; + const members = payloadPath.map(({ operation, service }, i) => { + if (!Reflect.has(serviceMap, service)) { + serviceMap[service] = { + name: service, + operations: {}, + }; + } + if (!Reflect.has(serviceMap[service].operations, operation)) { + serviceMap[service].operations[operation] = { + name: operation, + service: serviceMap[service], + pathElems: [], + }; + } + const pathElem = { + memberOf: path, + operation: serviceMap[service].operations[operation], + pathIdx: i, + visibilityIdx: visibilityIdx++, + }; + pathElem.operation.pathElems.push(pathElem); + if (path.focalIdx === -1 && service === focalNode.service && (focalNode.operation == null || operation == focalNode.operation)) { + path.focalIdx = i; + } + + Object.defineProperty(pathElem, 'distance', { + get: () => pathElem.pathIdx - pathElem.memberOf.focalIdx, + }); + + return pathElem; + }); + path.members = members; + + if (path.focalIdx === -1) { + throw new Error('A payload path lacked the focalNode'); + } + + return path; + }); + + return { + // operations: operationMap, + paths, + services: serviceMap, + } +} From 2efd6f4cd260fd9785c000377c9a3bde640d37bc Mon Sep 17 00:00:00 2001 From: Everett Ross Date: Thu, 9 May 2019 12:49:26 -0400 Subject: [PATCH 02/13] WIP - finish test cases for payload parsing Signed-off-by: Everett Ross --- .../DeepDependencyGraph/parse-payload.test.js | 23 +++++++++++++++++-- .../parse-payload.test.resource.js | 8 ++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js index 6f263b986e..ee20225962 100644 --- a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js +++ b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js @@ -23,8 +23,12 @@ import * as testResource from './parse-payload.test.resource'; describe('parse payload', () => { - function parsedOutputValidator(payload, focalIndices) { - const { paths, services } = parsePayload(payload, testResource.focalNode); + function parsedOutputValidator(payload, focalIndices, groupOperations = false) { + const { focalNode } = testResource; + const focalNodeArgument = groupOperations + ? { service: focalNode.service } + : focalNode; + const { paths, services } = parsePayload(payload, focalNodeArgument); const serviceNames = Object.keys(services); let membersProcessed = 0; expect(serviceNames).toEqual(Array.from(new Set(_map(_flatten(payload), 'service')))); @@ -66,4 +70,19 @@ describe('parse payload', () => { const { doubleFocalPath } = testResource; parsedOutputValidator([doubleFocalPath], [2]); }); + + it('checks both operation and service when calculating focalIdx when both are provided', () => { + const { almostDoubleFocalPath } = testResource; + parsedOutputValidator([almostDoubleFocalPath], [4]); + }); + + it('checks only service when calculating focalIdx when only service is provided', () => { + const { almostDoubleFocalPath } = testResource; + parsedOutputValidator([almostDoubleFocalPath], [2], true); + }); + + it('throws an error if a path lacks the focalNode', () => { + const { simplePath, noFocalPath, doubleFocalPath, focalNode } = testResource; + expect(() => parsePayload([simplePath, noFocalPath, doubleFocalPath], focalNode)).toThrowError(); + }); }); diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resource.js b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resource.js index 846cd3c6ed..89884cf071 100644 --- a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resource.js +++ b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resource.js @@ -14,10 +14,14 @@ const simpleNodeMaker = label => ({ operation: `${label}Operation`, - service: `${label}service`, + service: `${label}Service`, }); export const focalNode = simpleNodeMaker('focal'); +export const sameFocalServiceNode = { + operation: `not-${focalNode.operation}`, + service: focalNode.service, +}; const pathLengthener = path => { const prequels = []; @@ -47,4 +51,6 @@ export const longSimplePath = pathLengthener([firstNode, beforeNode, focalNode, const midNode = simpleNodeMaker('mid'); +export const noFocalPath = [firstNode, beforeNode, midNode, afterNode, lastNode]; export const doubleFocalPath = [firstNode, beforeNode, focalNode, midNode, focalNode, afterNode, lastNode]; +export const almostDoubleFocalPath = [firstNode, beforeNode, sameFocalServiceNode, midNode, focalNode, afterNode, lastNode]; From 3c945550748091f619ceb6fb0fe03f09881b56ad Mon Sep 17 00:00:00 2001 From: Everett Ross Date: Fri, 10 May 2019 10:53:11 -0400 Subject: [PATCH 03/13] Clean up variable names, add new lines and kwargs Signed-off-by: Everett Ross --- .../DeepDependencyGraph/parse-payload.test.js | 54 +++++++------ .../parse-payload.test.resource.js | 56 ------------- .../parse-payload.test.resources.js | 78 +++++++++++++++++++ .../DeepDependencyGraph/parse-payload.tsx | 58 +++++++------- 4 files changed, 130 insertions(+), 116 deletions(-) delete mode 100644 packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resource.js create mode 100644 packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resources.js diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js index ee20225962..57462abf23 100644 --- a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js +++ b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js @@ -12,30 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -// const fs = require('fs'); - import _filter from 'lodash/filter'; import _flatten from 'lodash/flatten'; import _map from 'lodash/map'; import parsePayload from './parse-payload'; -import * as testResource from './parse-payload.test.resource'; +import * as testResources from './parse-payload.test.resources'; describe('parse payload', () => { - - function parsedOutputValidator(payload, focalIndices, groupOperations = false) { - const { focalNode } = testResource; - const focalNodeArgument = groupOperations - ? { service: focalNode.service } - : focalNode; - const { paths, services } = parsePayload(payload, focalNodeArgument); + function parsedOutputValidator({ paths: payload, focalIndices, groupOperations = false }) { + const { focalPathElem } = testResources; + const focalPathElemArgument = groupOperations ? { service: focalPathElem.service } : focalPathElem; + const { paths, services } = parsePayload(payload, focalPathElemArgument); const serviceNames = Object.keys(services); - let membersProcessed = 0; + expect(serviceNames).toEqual(Array.from(new Set(_map(_flatten(payload), 'service')))); serviceNames.forEach(serviceName => { - expect(Object.keys(services[serviceName].operations)).toEqual(Array.from(new Set(_map(_filter(_flatten(payload), { service: serviceName}), 'operation')))); + expect(Object.keys(services[serviceName].operations)).toEqual( + Array.from(new Set(_map(_filter(_flatten(payload), { service: serviceName }), 'operation'))) + ); }); + let membersProcessed = 0; paths.forEach((path, pathResultIndex) => { expect(path.focalIdx).toBe(focalIndices[pathResultIndex]); path.members.forEach((member, memberResultIndex) => { @@ -52,37 +50,37 @@ describe('parse payload', () => { } it('parses an extremely simple payload', () => { - const { simplePath } = testResource; - parsedOutputValidator([simplePath], [2]); + const { simplePath } = testResources; + parsedOutputValidator({ paths: [simplePath], focalIndices: [2] }); }); it('parses a path with multiple operations per service and multiple services per operation', () => { - const { longSimplePath } = testResource; - parsedOutputValidator([longSimplePath], [6]); + const { longSimplePath } = testResources; + parsedOutputValidator({ paths: [longSimplePath], focalIndices: [6] }); }); it('parses a payload with significant overlap between paths', () => { - const { simplePath, longSimplePath } = testResource; - parsedOutputValidator([simplePath, longSimplePath], [2, 6]); + const { simplePath, longSimplePath } = testResources; + parsedOutputValidator({ paths: [simplePath, longSimplePath], focalIndices: [2, 6] }); }); - it('parses a path that contains the focal node twice', () => { - const { doubleFocalPath } = testResource; - parsedOutputValidator([doubleFocalPath], [2]); + it('parses a path that contains the focal path elem twice', () => { + const { doubleFocalPath } = testResources; + parsedOutputValidator({ paths: [doubleFocalPath], focalIndices: [2] }); }); it('checks both operation and service when calculating focalIdx when both are provided', () => { - const { almostDoubleFocalPath } = testResource; - parsedOutputValidator([almostDoubleFocalPath], [4]); + const { almostDoubleFocalPath } = testResources; + parsedOutputValidator({ paths: [almostDoubleFocalPath], focalIndices: [4] }); }); it('checks only service when calculating focalIdx when only service is provided', () => { - const { almostDoubleFocalPath } = testResource; - parsedOutputValidator([almostDoubleFocalPath], [2], true); + const { almostDoubleFocalPath } = testResources; + parsedOutputValidator({ paths: [almostDoubleFocalPath], focalIndices: [2], groupOperations: true }); }); - it('throws an error if a path lacks the focalNode', () => { - const { simplePath, noFocalPath, doubleFocalPath, focalNode } = testResource; - expect(() => parsePayload([simplePath, noFocalPath, doubleFocalPath], focalNode)).toThrowError(); + it('throws an error if a path lacks the focalPathElem', () => { + const { simplePath, noFocalPath, doubleFocalPath, focalPathElem } = testResources; + expect(() => parsePayload([simplePath, noFocalPath, doubleFocalPath], focalPathElem)).toThrowError(); }); }); diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resource.js b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resource.js deleted file mode 100644 index 89884cf071..0000000000 --- a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resource.js +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2019 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -const simpleNodeMaker = label => ({ - operation: `${label}Operation`, - service: `${label}Service`, -}); - -export const focalNode = simpleNodeMaker('focal'); -export const sameFocalServiceNode = { - operation: `not-${focalNode.operation}`, - service: focalNode.service, -}; - -const pathLengthener = path => { - const prequels = []; - const sequels = []; - path.forEach(({ operation, service }) => { - if (operation !== focalNode.operation && service !== focalNode.service) { - prequels.push({ - operation: `prequel-${operation}`, - service, - }); - sequels.push({ - operation, - service: `sequel-${service}`, - }); - } - }); - return [...prequels, ...path, ...sequels]; -} - -const firstNode = simpleNodeMaker('first'); -const beforeNode = simpleNodeMaker('before'); -const afterNode = simpleNodeMaker('after'); -const lastNode = simpleNodeMaker('last'); - -export const simplePath = [firstNode, beforeNode, focalNode, afterNode, lastNode]; -export const longSimplePath = pathLengthener([firstNode, beforeNode, focalNode, afterNode, lastNode]); - -const midNode = simpleNodeMaker('mid'); - -export const noFocalPath = [firstNode, beforeNode, midNode, afterNode, lastNode]; -export const doubleFocalPath = [firstNode, beforeNode, focalNode, midNode, focalNode, afterNode, lastNode]; -export const almostDoubleFocalPath = [firstNode, beforeNode, sameFocalServiceNode, midNode, focalNode, afterNode, lastNode]; diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resources.js b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resources.js new file mode 100644 index 0000000000..d91b11541c --- /dev/null +++ b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resources.js @@ -0,0 +1,78 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +const simplePathElemMaker = label => ({ + operation: `${label}Operation`, + service: `${label}Service`, +}); + +export const focalPathElem = simplePathElemMaker('focal'); +export const sameFocalServicePathElem = { + operation: `not-${focalPathElem.operation}`, + service: focalPathElem.service, +}; + +const pathLengthener = path => { + const prequels = []; + const sequels = []; + path.forEach(({ operation, service }) => { + if (operation !== focalPathElem.operation && service !== focalPathElem.service) { + prequels.push({ + operation: `prequel-${operation}`, + service, + }); + sequels.push({ + operation, + service: `sequel-${service}`, + }); + } + }); + return [...prequels, ...path, ...sequels]; +}; + +const firstPathElem = simplePathElemMaker('first'); +const beforePathElem = simplePathElemMaker('before'); +const afterPathElem = simplePathElemMaker('after'); +const lastPathElem = simplePathElemMaker('last'); + +export const simplePath = [firstPathElem, beforePathElem, focalPathElem, afterPathElem, lastPathElem]; +export const longSimplePath = pathLengthener([ + firstPathElem, + beforePathElem, + focalPathElem, + afterPathElem, + lastPathElem, +]); + +const midPathElem = simplePathElemMaker('mid'); + +export const noFocalPath = [firstPathElem, beforePathElem, midPathElem, afterPathElem, lastPathElem]; +export const doubleFocalPath = [ + firstPathElem, + beforePathElem, + focalPathElem, + midPathElem, + focalPathElem, + afterPathElem, + lastPathElem, +]; +export const almostDoubleFocalPath = [ + firstPathElem, + beforePathElem, + sameFocalServicePathElem, + midPathElem, + focalPathElem, + afterPathElem, + lastPathElem, +]; diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx index 5049276d20..d9c0717550 100644 --- a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx +++ b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx @@ -17,51 +17,41 @@ type TPayload = { service: string; }[][]; -type TService = { // de-duped +type TService = { name: string; operations: Record; - /* static service-based data added here later, e.g.: tier, PD link, uOwn link */ -} +}; -type TOperation = { // de-duped +type TOperation = { name: string; pathElems: TPathElem[]; service: TService; - /* static operation-based data added here later, e.g.: SLA(?) */ -} +}; -type TPathElem = { // super-duped +type TPathElem = { memberOf: TPath; operation: TOperation; - pathIdx: number; // in conjunction with focalIdx on TPath this can be used to calculate distance, isFocal, up/down stream + pathIdx: number; visibilityIdx: number; - /* dynamic, path-based, node data added here later, e.g.: contextual error rate */ -} +}; - -type TPath = { // One TPath per payload path - focalIdx: number; // Index of focal node in this path, helps with hops management +type TPath = { + focalIdx: number; members: TPathElem[]; - /* dynamic, path data added here later, e.g.: QPS */ -} - -/* -type TGraphEdge: { // Derived data - pathEdges: TPathEdge[]; -} - */ +}; type TOperationMap = Record; type TServiceMap = Record; type TParsedPayload = { - // operations: TOperationMap; paths: TPath[]; services: TServiceMap; -} +}; -export default function parsePayload(payload: TPayload, focalNode: { service: string, operation?: string }): TParsedPayload { - // const operationMap: TOperationMap = {}; +export default function parsePayload( + payload: TPayload, + { service: focalService, operation: focalOperation }: { service: string; operation?: string } +): TParsedPayload { const serviceMap: TServiceMap = {}; let visibilityIdx = 0; @@ -71,6 +61,7 @@ export default function parsePayload(payload: TPayload, focalNode: { service: st focalIdx: -1, members: [], }; + const members = payloadPath.map(({ operation, service }, i) => { if (!Reflect.has(serviceMap, service)) { serviceMap[service] = { @@ -91,14 +82,18 @@ export default function parsePayload(payload: TPayload, focalNode: { service: st pathIdx: i, visibilityIdx: visibilityIdx++, }; - pathElem.operation.pathElems.push(pathElem); - if (path.focalIdx === -1 && service === focalNode.service && (focalNode.operation == null || operation == focalNode.operation)) { - path.focalIdx = i; - } - Object.defineProperty(pathElem, 'distance', { get: () => pathElem.pathIdx - pathElem.memberOf.focalIdx, }); + pathElem.operation.pathElems.push(pathElem); + + if ( + path.focalIdx === -1 && + service === focalService && + (focalOperation == null || operation === focalOperation) + ) { + path.focalIdx = i; + } return pathElem; }); @@ -112,8 +107,7 @@ export default function parsePayload(payload: TPayload, focalNode: { service: st }); return { - // operations: operationMap, paths, services: serviceMap, - } + }; } From de92808e25ca3a23406a4af4077d8f1fe8cf56b0 Mon Sep 17 00:00:00 2001 From: Everett Ross Date: Fri, 10 May 2019 10:59:32 -0400 Subject: [PATCH 04/13] Remove unused type, reorder variables Signed-off-by: Everett Ross --- .../DeepDependencyGraph/parse-payload.test.resources.js | 7 +++---- .../src/utils/DeepDependencyGraph/parse-payload.tsx | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resources.js b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resources.js index d91b11541c..6254a474f7 100644 --- a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resources.js +++ b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resources.js @@ -18,7 +18,8 @@ const simplePathElemMaker = label => ({ }); export const focalPathElem = simplePathElemMaker('focal'); -export const sameFocalServicePathElem = { + +const sameFocalServicePathElem = { operation: `not-${focalPathElem.operation}`, service: focalPathElem.service, }; @@ -43,6 +44,7 @@ const pathLengthener = path => { const firstPathElem = simplePathElemMaker('first'); const beforePathElem = simplePathElemMaker('before'); +const midPathElem = simplePathElemMaker('mid'); const afterPathElem = simplePathElemMaker('after'); const lastPathElem = simplePathElemMaker('last'); @@ -54,9 +56,6 @@ export const longSimplePath = pathLengthener([ afterPathElem, lastPathElem, ]); - -const midPathElem = simplePathElemMaker('mid'); - export const noFocalPath = [firstPathElem, beforePathElem, midPathElem, afterPathElem, lastPathElem]; export const doubleFocalPath = [ firstPathElem, diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx index d9c0717550..b582086c23 100644 --- a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx +++ b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx @@ -40,7 +40,6 @@ type TPath = { members: TPathElem[]; }; -type TOperationMap = Record; type TServiceMap = Record; type TParsedPayload = { From 757501f62d709d87d513a335053f59f60146535b Mon Sep 17 00:00:00 2001 From: Everett Ross Date: Mon, 13 May 2019 13:51:06 -0400 Subject: [PATCH 05/13] WIP - change to maps, improve type names Signed-off-by: Everett Ross --- .../DeepDependencyGraph/parse-payload.test.js | 9 +- .../parse-payload.test.resources.js | 8 +- .../DeepDependencyGraph/parse-payload.tsx | 117 ++++++++++-------- 3 files changed, 68 insertions(+), 66 deletions(-) diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js index 57462abf23..a476f3e941 100644 --- a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js +++ b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js @@ -24,12 +24,11 @@ describe('parse payload', () => { const { focalPathElem } = testResources; const focalPathElemArgument = groupOperations ? { service: focalPathElem.service } : focalPathElem; const { paths, services } = parsePayload(payload, focalPathElemArgument); - const serviceNames = Object.keys(services); - expect(serviceNames).toEqual(Array.from(new Set(_map(_flatten(payload), 'service')))); - serviceNames.forEach(serviceName => { - expect(Object.keys(services[serviceName].operations)).toEqual( - Array.from(new Set(_map(_filter(_flatten(payload), { service: serviceName }), 'operation'))) + expect(new Set(services.keys())).toEqual(new Set(_map(_flatten(payload), 'service'))); + services.forEach((service, serviceName) => { + expect(new Set(service.operations.keys())).toEqual( + new Set(_map(_filter(_flatten(payload), { service: serviceName }), 'operation')) ); }); diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resources.js b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resources.js index 6254a474f7..2a2ab3bf34 100644 --- a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resources.js +++ b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resources.js @@ -49,13 +49,7 @@ const afterPathElem = simplePathElemMaker('after'); const lastPathElem = simplePathElemMaker('last'); export const simplePath = [firstPathElem, beforePathElem, focalPathElem, afterPathElem, lastPathElem]; -export const longSimplePath = pathLengthener([ - firstPathElem, - beforePathElem, - focalPathElem, - afterPathElem, - lastPathElem, -]); +export const longSimplePath = pathLengthener(simplePath); export const noFocalPath = [firstPathElem, beforePathElem, midPathElem, afterPathElem, lastPathElem]; export const doubleFocalPath = [ firstPathElem, diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx index b582086c23..b72fabbfd1 100644 --- a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx +++ b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx @@ -12,93 +12,101 @@ // See the License for the specific language governing permissions and // limitations under the License. -type TPayload = { +type TDdgPayload = { operation: string; service: string; }[][]; -type TService = { +type TDdgService = { name: string; - operations: Record; + operations: Map; }; -type TOperation = { +type TDdgOperation = { name: string; - pathElems: TPathElem[]; - service: TService; + pathElems: PathElem[]; + service: TDdgService; }; -type TPathElem = { - memberOf: TPath; - operation: TOperation; - pathIdx: number; - visibilityIdx: number; -}; - -type TPath = { +type TDdgPath = { focalIdx: number; - members: TPathElem[]; + members: PathElem[]; }; -type TServiceMap = Record; +type TDdgServiceMap = Map; -type TParsedPayload = { - paths: TPath[]; - services: TServiceMap; +type TDdgParsedPayload = { + paths: TDdgPath[]; + services: TDdgServiceMap; }; +class PathElem { + memberOf: TDdgPath; + operation: TDdgOperation; + pathIdx: number; + visibilityIdx: number; + + constructor({ path, operation, pathIdx, visibilityIdx }: { + path: TDdgPath; + operation: TDdgOperation; + pathIdx: number; + visibilityIdx: number; + }) { + this.memberOf = path; + this.operation = operation; + this.pathIdx = pathIdx; + this.visibilityIdx = visibilityIdx; + operation.pathElems.push(this); + } + + get distance() { + return this.pathIdx - this.memberOf.focalIdx; + } +} + +type TDdgPathElemsByDistance = Map; + export default function parsePayload( - payload: TPayload, + payload: TDdgPayload, { service: focalService, operation: focalOperation }: { service: string; operation?: string } -): TParsedPayload { - const serviceMap: TServiceMap = {}; +): TDdgParsedPayload { + const serviceMap: TDdgServiceMap = new Map(); + const pathElemsByDistance: TDdgPathElemsByDistance = new Map(); let visibilityIdx = 0; const paths = payload.map(payloadPath => { // path with stand-in values in order to have obj to which to assign memberOf for each pathElem. - const path: TPath = { - focalIdx: -1, - members: [], - }; - - const members = payloadPath.map(({ operation, service }, i) => { - if (!Reflect.has(serviceMap, service)) { - serviceMap[service] = { - name: service, - operations: {}, - }; + const path = {} as TDdgPath; + + const members = payloadPath.map(({ operation: operationName, service: serviceName }, i) => { + if (!serviceMap.has(serviceName)) { + serviceMap.set(serviceName, { + name: serviceName, + operations: new Map(), + }); } - if (!Reflect.has(serviceMap[service].operations, operation)) { - serviceMap[service].operations[operation] = { - name: operation, - service: serviceMap[service], + const service = serviceMap.get(serviceName) as TDdgService; + if (!service.operations.has(operationName)) { + service.operations.set(operationName, { + name: operationName, + service: service, pathElems: [], - }; + }); } - const pathElem = { - memberOf: path, - operation: serviceMap[service].operations[operation], - pathIdx: i, - visibilityIdx: visibilityIdx++, - }; - Object.defineProperty(pathElem, 'distance', { - get: () => pathElem.pathIdx - pathElem.memberOf.focalIdx, - }); - pathElem.operation.pathElems.push(pathElem); - + const operation = service.operations.get(operationName) as TDdgOperation; if ( - path.focalIdx === -1 && - service === focalService && - (focalOperation == null || operation === focalOperation) + path.focalIdx == null && + serviceName === focalService && + (focalOperation == null || operationName === focalOperation) ) { path.focalIdx = i; } - return pathElem; + return new PathElem({ path, operation, pathIdx: i, visibilityIdx: visibilityIdx++ }); }); path.members = members; - if (path.focalIdx === -1) { + if (path.focalIdx == null) { throw new Error('A payload path lacked the focalNode'); } @@ -107,6 +115,7 @@ export default function parsePayload( return { paths, +, services: serviceMap, }; } From 002f485bcdccdc5f43dffe0ad4d693d22a04ce27 Mon Sep 17 00:00:00 2001 From: Everett Ross Date: Mon, 13 May 2019 16:01:33 -0400 Subject: [PATCH 06/13] WIP-ensure +correlation for visibilityIdx&distance Signed-off-by: Everett Ross --- .../DeepDependencyGraph/parse-payload.test.js | 30 ++++++-- .../DeepDependencyGraph/parse-payload.tsx | 75 ++++++++++++++++--- 2 files changed, 87 insertions(+), 18 deletions(-) diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js index a476f3e941..337ff7bd58 100644 --- a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js +++ b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js @@ -32,7 +32,9 @@ describe('parse payload', () => { ); }); - let membersProcessed = 0; + const expectedVisibilityIndices = []; + const visibilityIndicesToDistance = new Map(); + paths.forEach((path, pathResultIndex) => { expect(path.focalIdx).toBe(focalIndices[pathResultIndex]); path.members.forEach((member, memberResultIndex) => { @@ -43,9 +45,23 @@ describe('parse payload', () => { expect(operation.pathElems.includes(member)).toBe(true); expect(operation.service.name).toBe(payload[pathResultIndex][memberResultIndex].service); expect(pathIdx).toBe(memberResultIndex); - expect(visibilityIdx).toBe(membersProcessed++); + + expectedVisibilityIndices.push(expectedVisibilityIndices.length); + visibilityIndicesToDistance.set(visibilityIdx, distance); }); }); + + const orderedVisibilityIndices = Array.from(visibilityIndicesToDistance.keys()).sort((a, b) => a - b); + expect(orderedVisibilityIndices).toEqual(expectedVisibilityIndices); + let distance = 0; + orderedVisibilityIndices.forEach(orderedIdx => { + const currentDistance = Math.abs(visibilityIndicesToDistance.get(orderedIdx)); + if (currentDistance < distance) { + throw new Error('Distance did not increase or stay equal as visibilityIdx increased'); + } else if (currentDistance > distance) { + distance = currentDistance; + } + }); } it('parses an extremely simple payload', () => { @@ -58,11 +74,6 @@ describe('parse payload', () => { parsedOutputValidator({ paths: [longSimplePath], focalIndices: [6] }); }); - it('parses a payload with significant overlap between paths', () => { - const { simplePath, longSimplePath } = testResources; - parsedOutputValidator({ paths: [simplePath, longSimplePath], focalIndices: [2, 6] }); - }); - it('parses a path that contains the focal path elem twice', () => { const { doubleFocalPath } = testResources; parsedOutputValidator({ paths: [doubleFocalPath], focalIndices: [2] }); @@ -78,6 +89,11 @@ describe('parse payload', () => { parsedOutputValidator({ paths: [almostDoubleFocalPath], focalIndices: [2], groupOperations: true }); }); + it('parses a payload with significant overlap between paths', () => { + const { simplePath, longSimplePath, doubleFocalPath, almostDoubleFocalPath } = testResources; + parsedOutputValidator({ paths: [simplePath, longSimplePath, doubleFocalPath, almostDoubleFocalPath], focalIndices: [2, 6, 2, 4] }); + }); + it('throws an error if a path lacks the focalPathElem', () => { const { simplePath, noFocalPath, doubleFocalPath, focalPathElem } = testResources; expect(() => parsePayload([simplePath, noFocalPath, doubleFocalPath], focalPathElem)).toThrowError(); diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx index b72fabbfd1..8cffe7ed65 100644 --- a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx +++ b/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx @@ -35,8 +35,11 @@ type TDdgPath = { type TDdgServiceMap = Map; +type TDdgPathElemsByDistance = Map; + type TDdgParsedPayload = { paths: TDdgPath[]; + pathElemsByDistance: TDdgPathElemsByDistance; services: TDdgServiceMap; }; @@ -44,35 +47,46 @@ class PathElem { memberOf: TDdgPath; operation: TDdgOperation; pathIdx: number; - visibilityIdx: number; + visibilityIdx?: number; - constructor({ path, operation, pathIdx, visibilityIdx }: { + constructor({ path, operation, pathIdx /*, visibilityIdx */ }: { path: TDdgPath; operation: TDdgOperation; pathIdx: number; - visibilityIdx: number; + // visibilityIdx: number; }) { this.memberOf = path; this.operation = operation; this.pathIdx = pathIdx; - this.visibilityIdx = visibilityIdx; + // this.visibilityIdx = visibilityIdx; operation.pathElems.push(this); } get distance() { return this.pathIdx - this.memberOf.focalIdx; } -} -type TDdgPathElemsByDistance = Map; + set visibiliityIdx(visibiliityIdx: number) { + this.visibiliityIdx = visibiliityIdx; + } + + get visibiliityIdx(): number { + if (this.visibiliityIdx == null) { + throw new Error('Visibility Index was never set for this PathElem'); + } + return this.visibiliityIdx; + } +} export default function parsePayload( payload: TDdgPayload, { service: focalService, operation: focalOperation }: { service: string; operation?: string } ): TDdgParsedPayload { + let furthestUpstream = 0; + let furthestDownstream = 0; const serviceMap: TDdgServiceMap = new Map(); const pathElemsByDistance: TDdgPathElemsByDistance = new Map(); - let visibilityIdx = 0; + // let visibilityIdx = 0; const paths = payload.map(payloadPath => { // path with stand-in values in order to have obj to which to assign memberOf for each pathElem. @@ -101,21 +115,60 @@ export default function parsePayload( ) { path.focalIdx = i; } + /* + if (path.focalIdx != null) { + if (!pathElemsByDistance.has( + } + */ - return new PathElem({ path, operation, pathIdx: i, visibilityIdx: visibilityIdx++ }); + return new PathElem({ path, operation, pathIdx: i /* , visibilityIdx: visibilityIdx++ */ }); }); - path.members = members; - if (path.focalIdx == null) { throw new Error('A payload path lacked the focalNode'); } + path.members = members; + + members.forEach(member => { + if (!pathElemsByDistance.has(member.distance)) { + pathElemsByDistance.set(member.distance, []); + } + (pathElemsByDistance.get(member.distance) as PathElem[]).push(member); + if (member.distance < furthestDownstream) { + furthestDownstream = member.distance; + } + if (member.distance > furthestUpstream) { + furthestUpstream = member.distance; + } + }); return path; }); + // console.log(pathElemsByDistance); + // console.log(furthestUpstream, furthestDownstream); + + let upstream = 1; + let downstream = 0; + let visibilityIdx = 0; + while(upstream <= furthestUpstream || downstream >= furthestDownstream) { + // console.log(upstream, downstream); + let nextToIndex: PathElem[]; + if ((Math.abs(downstream) < upstream && downstream >= furthestDownstream) || upstream > furthestUpstream) { + nextToIndex = pathElemsByDistance.get(downstream--) as PathElem[]; + } else { + nextToIndex = pathElemsByDistance.get(upstream++) as PathElem[]; + } + // console.log(nextToIndex); + nextToIndex.forEach(indexMe => { + indexMe.visibilityIdx = visibilityIdx++; + // console.log(indexMe); + }); + } + // console.log(pathElemsByDistance); + return { paths, -, + pathElemsByDistance, services: serviceMap, }; } From 2d138b13e31fd435d19d806c65eb19c13f27d683 Mon Sep 17 00:00:00 2001 From: Everett Ross Date: Tue, 14 May 2019 11:35:41 -0400 Subject: [PATCH 07/13] Move files, test PathElem class Signed-off-by: Everett Ross --- .../ddg}/parse-payload.test.js | 7 +- .../ddg}/parse-payload.test.resources.js | 0 .../ddg}/parse-payload.tsx | 112 ++++-------------- .../src/model/ddg/types/index.test.js | 63 ++++++++++ .../jaeger-ui/src/model/ddg/types/index.tsx | 77 ++++++++++++ 5 files changed, 169 insertions(+), 90 deletions(-) rename packages/jaeger-ui/src/{utils/DeepDependencyGraph => model/ddg}/parse-payload.test.js (94%) rename packages/jaeger-ui/src/{utils/DeepDependencyGraph => model/ddg}/parse-payload.test.resources.js (100%) rename packages/jaeger-ui/src/{utils/DeepDependencyGraph => model/ddg}/parse-payload.tsx (53%) create mode 100644 packages/jaeger-ui/src/model/ddg/types/index.test.js create mode 100644 packages/jaeger-ui/src/model/ddg/types/index.tsx diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js b/packages/jaeger-ui/src/model/ddg/parse-payload.test.js similarity index 94% rename from packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js rename to packages/jaeger-ui/src/model/ddg/parse-payload.test.js index 337ff7bd58..3b429c9070 100644 --- a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js +++ b/packages/jaeger-ui/src/model/ddg/parse-payload.test.js @@ -57,7 +57,7 @@ describe('parse payload', () => { orderedVisibilityIndices.forEach(orderedIdx => { const currentDistance = Math.abs(visibilityIndicesToDistance.get(orderedIdx)); if (currentDistance < distance) { - throw new Error('Distance did not increase or stay equal as visibilityIdx increased'); + throw new Error('Net distance did not increase or stay equal as visibilityIdx increased'); } else if (currentDistance > distance) { distance = currentDistance; } @@ -91,7 +91,10 @@ describe('parse payload', () => { it('parses a payload with significant overlap between paths', () => { const { simplePath, longSimplePath, doubleFocalPath, almostDoubleFocalPath } = testResources; - parsedOutputValidator({ paths: [simplePath, longSimplePath, doubleFocalPath, almostDoubleFocalPath], focalIndices: [2, 6, 2, 4] }); + parsedOutputValidator({ + paths: [simplePath, longSimplePath, doubleFocalPath, almostDoubleFocalPath], + focalIndices: [2, 6, 2, 4], + }); }); it('throws an error if a path lacks the focalPathElem', () => { diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resources.js b/packages/jaeger-ui/src/model/ddg/parse-payload.test.resources.js similarity index 100% rename from packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resources.js rename to packages/jaeger-ui/src/model/ddg/parse-payload.test.resources.js diff --git a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx b/packages/jaeger-ui/src/model/ddg/parse-payload.tsx similarity index 53% rename from packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx rename to packages/jaeger-ui/src/model/ddg/parse-payload.tsx index 8cffe7ed65..5df6f84221 100644 --- a/packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx +++ b/packages/jaeger-ui/src/model/ddg/parse-payload.tsx @@ -12,71 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -type TDdgPayload = { - operation: string; - service: string; -}[][]; - -type TDdgService = { - name: string; - operations: Map; -}; - -type TDdgOperation = { - name: string; - pathElems: PathElem[]; - service: TDdgService; -}; - -type TDdgPath = { - focalIdx: number; - members: PathElem[]; -}; - -type TDdgServiceMap = Map; - -type TDdgPathElemsByDistance = Map; - -type TDdgParsedPayload = { - paths: TDdgPath[]; - pathElemsByDistance: TDdgPathElemsByDistance; - services: TDdgServiceMap; -}; - -class PathElem { - memberOf: TDdgPath; - operation: TDdgOperation; - pathIdx: number; - visibilityIdx?: number; - - constructor({ path, operation, pathIdx /*, visibilityIdx */ }: { - path: TDdgPath; - operation: TDdgOperation; - pathIdx: number; - // visibilityIdx: number; - }) { - this.memberOf = path; - this.operation = operation; - this.pathIdx = pathIdx; - // this.visibilityIdx = visibilityIdx; - operation.pathElems.push(this); - } - - get distance() { - return this.pathIdx - this.memberOf.focalIdx; - } - - set visibiliityIdx(visibiliityIdx: number) { - this.visibiliityIdx = visibiliityIdx; - } - - get visibiliityIdx(): number { - if (this.visibiliityIdx == null) { - throw new Error('Visibility Index was never set for this PathElem'); - } - return this.visibiliityIdx; - } -} +import { + TDdgPayload, + TDdgService, + TDdgOperation, + TDdgPath, + TDdgServiceMap, + TDdgPathElemsByDistance, + TDdgParsedPayload, + PathElem, +} from './types'; export default function parsePayload( payload: TDdgPayload, @@ -86,7 +31,6 @@ export default function parsePayload( let furthestDownstream = 0; const serviceMap: TDdgServiceMap = new Map(); const pathElemsByDistance: TDdgPathElemsByDistance = new Map(); - // let visibilityIdx = 0; const paths = payload.map(payloadPath => { // path with stand-in values in order to have obj to which to assign memberOf for each pathElem. @@ -103,7 +47,7 @@ export default function parsePayload( if (!service.operations.has(operationName)) { service.operations.set(operationName, { name: operationName, - service: service, + service, pathElems: [], }); } @@ -115,13 +59,8 @@ export default function parsePayload( ) { path.focalIdx = i; } - /* - if (path.focalIdx != null) { - if (!pathElemsByDistance.has( - } - */ - return new PathElem({ path, operation, pathIdx: i /* , visibilityIdx: visibilityIdx++ */ }); + return new PathElem({ path, operation, pathIdx: i }); }); if (path.focalIdx == null) { throw new Error('A payload path lacked the focalNode'); @@ -144,27 +83,24 @@ export default function parsePayload( return path; }); - // console.log(pathElemsByDistance); - // console.log(furthestUpstream, furthestDownstream); - let upstream = 1; let downstream = 0; let visibilityIdx = 0; - while(upstream <= furthestUpstream || downstream >= furthestDownstream) { - // console.log(upstream, downstream); - let nextToIndex: PathElem[]; - if ((Math.abs(downstream) < upstream && downstream >= furthestDownstream) || upstream > furthestUpstream) { - nextToIndex = pathElemsByDistance.get(downstream--) as PathElem[]; + function setPathElemVisibilityIdx(pathElem: PathElem) { + pathElem.visibilityIdx = visibilityIdx++; // eslint-disable-line no-param-reassign + } + while (upstream <= furthestUpstream || downstream >= furthestDownstream) { + let nextArrayToIndex: PathElem[]; + if ( + (Math.abs(downstream) < upstream && downstream >= furthestDownstream) || + upstream > furthestUpstream + ) { + nextArrayToIndex = pathElemsByDistance.get(downstream--) as PathElem[]; } else { - nextToIndex = pathElemsByDistance.get(upstream++) as PathElem[]; + nextArrayToIndex = pathElemsByDistance.get(upstream++) as PathElem[]; } - // console.log(nextToIndex); - nextToIndex.forEach(indexMe => { - indexMe.visibilityIdx = visibilityIdx++; - // console.log(indexMe); - }); + nextArrayToIndex.forEach(setPathElemVisibilityIdx); } - // console.log(pathElemsByDistance); return { paths, diff --git a/packages/jaeger-ui/src/model/ddg/types/index.test.js b/packages/jaeger-ui/src/model/ddg/types/index.test.js new file mode 100644 index 0000000000..741cde22a9 --- /dev/null +++ b/packages/jaeger-ui/src/model/ddg/types/index.test.js @@ -0,0 +1,63 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { PathElem } from '.'; + +describe('ddg classes', () => { + describe('PathElem', () => { + const testPath = { + focalIdx: 5, + }; + const testOperation = {}; + const testPathIdx = 3; + const testVisibilityIdx = 105; + const preexistingPathElems = ['fillerPathElem0', 'fillerPathElem1']; + let pathElem; + + beforeEach(() => { + testOperation.pathElems = preexistingPathElems.slice(); + pathElem = new PathElem({ path: testPath, operation: testOperation, pathIdx: testPathIdx }); + }); + + it('initializes instance properties', () => { + expect(pathElem.memberOf).toBe(testPath); + expect(pathElem.operation).toBe(testOperation); + expect(pathElem.pathIdx).toBe(testPathIdx); + }); + + it('adds itself to provided operation', () => { + expect(testOperation.pathElems).toEqual([...preexistingPathElems, pathElem]); + }); + + it('calculates distance', () => { + expect(pathElem.distance).toBe(-2); + }); + + it('sets visibilityIdx', () => { + pathElem.visibilityIdx = testVisibilityIdx; + expect(pathElem.visibilityIdx).toBe(testVisibilityIdx); + }); + + it('errors when trying to access unset visibilityIdx', () => { + expect(() => pathElem.visibilityIdx).toThrowError(); + }); + + it('errors when trying to override visibilityIdx', () => { + pathElem.visibilityIdx = testVisibilityIdx; + expect(() => { + pathElem.visibilityIdx = testVisibilityIdx; + }).toThrowError(); + }); + }); +}); diff --git a/packages/jaeger-ui/src/model/ddg/types/index.tsx b/packages/jaeger-ui/src/model/ddg/types/index.tsx new file mode 100644 index 0000000000..0a5121fac8 --- /dev/null +++ b/packages/jaeger-ui/src/model/ddg/types/index.tsx @@ -0,0 +1,77 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export type TDdgPayload = { + operation: string; + service: string; +}[][]; + +export type TDdgService = { + name: string; + operations: Map; +}; + +export type TDdgOperation = { + name: string; + pathElems: PathElem[]; + service: TDdgService; +}; + +export type TDdgPath = { + focalIdx: number; + members: PathElem[]; +}; + +export type TDdgServiceMap = Map; + +export type TDdgPathElemsByDistance = Map; + +export type TDdgParsedPayload = { + paths: TDdgPath[]; + pathElemsByDistance: TDdgPathElemsByDistance; + services: TDdgServiceMap; +}; + +export class PathElem { + memberOf: TDdgPath; + operation: TDdgOperation; + pathIdx: number; + private _visibilityIdx?: number; + + constructor({ path, operation, pathIdx }: { path: TDdgPath; operation: TDdgOperation; pathIdx: number }) { + this.memberOf = path; + this.operation = operation; + this.pathIdx = pathIdx; + operation.pathElems.push(this); + } + + get distance() { + return this.pathIdx - this.memberOf.focalIdx; + } + + set visibilityIdx(visibiliityIdx: number) { + if (this._visibilityIdx == null) { + this._visibilityIdx = visibiliityIdx; + } else { + throw new Error('Visibility Index cannot be changed once set'); + } + } + + get visibilityIdx(): number { + if (this._visibilityIdx == null) { + throw new Error('Visibility Index was never set for this PathElem'); + } + return this._visibilityIdx; + } +} From 34a8dce1cb95b887fbb7b7844b9e38778b4c6a88 Mon Sep 17 00:00:00 2001 From: Everett Ross Date: Tue, 14 May 2019 12:04:32 -0400 Subject: [PATCH 08/13] Remove unnecessary up/down stream distance tracker Signed-off-by: Everett Ross --- .../src/model/ddg/parse-payload.test.js | 6 ++--- .../jaeger-ui/src/model/ddg/parse-payload.tsx | 27 ++++++++----------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/jaeger-ui/src/model/ddg/parse-payload.test.js b/packages/jaeger-ui/src/model/ddg/parse-payload.test.js index 3b429c9070..71ed3b907f 100644 --- a/packages/jaeger-ui/src/model/ddg/parse-payload.test.js +++ b/packages/jaeger-ui/src/model/ddg/parse-payload.test.js @@ -20,9 +20,9 @@ import parsePayload from './parse-payload'; import * as testResources from './parse-payload.test.resources'; describe('parse payload', () => { - function parsedOutputValidator({ paths: payload, focalIndices, groupOperations = false }) { + function parsedOutputValidator({ paths: payload, focalIndices, ignoreFocalOperation = false }) { const { focalPathElem } = testResources; - const focalPathElemArgument = groupOperations ? { service: focalPathElem.service } : focalPathElem; + const focalPathElemArgument = ignoreFocalOperation ? { service: focalPathElem.service } : focalPathElem; const { paths, services } = parsePayload(payload, focalPathElemArgument); expect(new Set(services.keys())).toEqual(new Set(_map(_flatten(payload), 'service'))); @@ -86,7 +86,7 @@ describe('parse payload', () => { it('checks only service when calculating focalIdx when only service is provided', () => { const { almostDoubleFocalPath } = testResources; - parsedOutputValidator({ paths: [almostDoubleFocalPath], focalIndices: [2], groupOperations: true }); + parsedOutputValidator({ paths: [almostDoubleFocalPath], focalIndices: [2], ignoreFocalOperation: true }); }); it('parses a payload with significant overlap between paths', () => { diff --git a/packages/jaeger-ui/src/model/ddg/parse-payload.tsx b/packages/jaeger-ui/src/model/ddg/parse-payload.tsx index 5df6f84221..884b5a456c 100644 --- a/packages/jaeger-ui/src/model/ddg/parse-payload.tsx +++ b/packages/jaeger-ui/src/model/ddg/parse-payload.tsx @@ -27,13 +27,10 @@ export default function parsePayload( payload: TDdgPayload, { service: focalService, operation: focalOperation }: { service: string; operation?: string } ): TDdgParsedPayload { - let furthestUpstream = 0; - let furthestDownstream = 0; const serviceMap: TDdgServiceMap = new Map(); const pathElemsByDistance: TDdgPathElemsByDistance = new Map(); const paths = payload.map(payloadPath => { - // path with stand-in values in order to have obj to which to assign memberOf for each pathElem. const path = {} as TDdgPath; const members = payloadPath.map(({ operation: operationName, service: serviceName }, i) => { @@ -44,6 +41,7 @@ export default function parsePayload( }); } const service = serviceMap.get(serviceName) as TDdgService; + if (!service.operations.has(operationName)) { service.operations.set(operationName, { name: operationName, @@ -52,6 +50,7 @@ export default function parsePayload( }); } const operation = service.operations.get(operationName) as TDdgOperation; + if ( path.focalIdx == null && serviceName === focalService && @@ -62,21 +61,17 @@ export default function parsePayload( return new PathElem({ path, operation, pathIdx: i }); }); + if (path.focalIdx == null) { throw new Error('A payload path lacked the focalNode'); } - path.members = members; + path.members = members; members.forEach(member => { - if (!pathElemsByDistance.has(member.distance)) { - pathElemsByDistance.set(member.distance, []); - } - (pathElemsByDistance.get(member.distance) as PathElem[]).push(member); - if (member.distance < furthestDownstream) { - furthestDownstream = member.distance; - } - if (member.distance > furthestUpstream) { - furthestUpstream = member.distance; + if (pathElemsByDistance.has(member.distance)) { + (pathElemsByDistance.get(member.distance) as PathElem[]).push(member); + } else { + pathElemsByDistance.set(member.distance, [member]); } }); @@ -89,11 +84,11 @@ export default function parsePayload( function setPathElemVisibilityIdx(pathElem: PathElem) { pathElem.visibilityIdx = visibilityIdx++; // eslint-disable-line no-param-reassign } - while (upstream <= furthestUpstream || downstream >= furthestDownstream) { + while (pathElemsByDistance.has(upstream) || pathElemsByDistance.has(downstream)) { let nextArrayToIndex: PathElem[]; if ( - (Math.abs(downstream) < upstream && downstream >= furthestDownstream) || - upstream > furthestUpstream + (Math.abs(downstream) < upstream && pathElemsByDistance.has(downstream)) || + !pathElemsByDistance.has(upstream) ) { nextArrayToIndex = pathElemsByDistance.get(downstream--) as PathElem[]; } else { From 1e898017c88d296dac0351bc424ae5893e06499a Mon Sep 17 00:00:00 2001 From: Everett Ross Date: Tue, 14 May 2019 14:38:39 -0400 Subject: [PATCH 09/13] Move and rename files Signed-off-by: Everett Ross --- ...oad.test.js => transform-ddg-data.test.js} | 32 +++++++++---------- ...s => transform-ddg-data.test.resources.js} | 0 ...rse-payload.tsx => transform-ddg-data.tsx} | 6 ++-- .../{types/index.test.js => types.test.js} | 6 ++-- .../model/ddg/{types/index.tsx => types.tsx} | 5 +-- 5 files changed, 25 insertions(+), 24 deletions(-) rename packages/jaeger-ui/src/model/ddg/{parse-payload.test.js => transform-ddg-data.test.js} (75%) rename packages/jaeger-ui/src/model/ddg/{parse-payload.test.resources.js => transform-ddg-data.test.resources.js} (100%) rename packages/jaeger-ui/src/model/ddg/{parse-payload.tsx => transform-ddg-data.tsx} (97%) rename packages/jaeger-ui/src/model/ddg/{types/index.test.js => types.test.js} (98%) rename packages/jaeger-ui/src/model/ddg/{types/index.tsx => types.tsx} (98%) diff --git a/packages/jaeger-ui/src/model/ddg/parse-payload.test.js b/packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.js similarity index 75% rename from packages/jaeger-ui/src/model/ddg/parse-payload.test.js rename to packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.js index 71ed3b907f..4067357844 100644 --- a/packages/jaeger-ui/src/model/ddg/parse-payload.test.js +++ b/packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.js @@ -16,14 +16,14 @@ import _filter from 'lodash/filter'; import _flatten from 'lodash/flatten'; import _map from 'lodash/map'; -import parsePayload from './parse-payload'; -import * as testResources from './parse-payload.test.resources'; +import transformDdgData from './transform-ddg-data'; +import * as testResources from './transform-ddg-data.test.resources'; -describe('parse payload', () => { - function parsedOutputValidator({ paths: payload, focalIndices, ignoreFocalOperation = false }) { +describe('transform ddg data', () => { + function outputValidator({ paths: payload, focalIndices, ignoreFocalOperation = false }) { const { focalPathElem } = testResources; const focalPathElemArgument = ignoreFocalOperation ? { service: focalPathElem.service } : focalPathElem; - const { paths, services } = parsePayload(payload, focalPathElemArgument); + const { paths, services } = transformDdgData(payload, focalPathElemArgument); expect(new Set(services.keys())).toEqual(new Set(_map(_flatten(payload), 'service'))); services.forEach((service, serviceName) => { @@ -64,34 +64,34 @@ describe('parse payload', () => { }); } - it('parses an extremely simple payload', () => { + it('transforms an extremely simple payload', () => { const { simplePath } = testResources; - parsedOutputValidator({ paths: [simplePath], focalIndices: [2] }); + outputValidator({ paths: [simplePath], focalIndices: [2] }); }); - it('parses a path with multiple operations per service and multiple services per operation', () => { + it('transforms a path with multiple operations per service and multiple services per operation', () => { const { longSimplePath } = testResources; - parsedOutputValidator({ paths: [longSimplePath], focalIndices: [6] }); + outputValidator({ paths: [longSimplePath], focalIndices: [6] }); }); - it('parses a path that contains the focal path elem twice', () => { + it('transforms a path that contains the focal path elem twice', () => { const { doubleFocalPath } = testResources; - parsedOutputValidator({ paths: [doubleFocalPath], focalIndices: [2] }); + outputValidator({ paths: [doubleFocalPath], focalIndices: [2] }); }); it('checks both operation and service when calculating focalIdx when both are provided', () => { const { almostDoubleFocalPath } = testResources; - parsedOutputValidator({ paths: [almostDoubleFocalPath], focalIndices: [4] }); + outputValidator({ paths: [almostDoubleFocalPath], focalIndices: [4] }); }); it('checks only service when calculating focalIdx when only service is provided', () => { const { almostDoubleFocalPath } = testResources; - parsedOutputValidator({ paths: [almostDoubleFocalPath], focalIndices: [2], ignoreFocalOperation: true }); + outputValidator({ paths: [almostDoubleFocalPath], focalIndices: [2], ignoreFocalOperation: true }); }); - it('parses a payload with significant overlap between paths', () => { + it('transforms a payload with significant overlap between paths', () => { const { simplePath, longSimplePath, doubleFocalPath, almostDoubleFocalPath } = testResources; - parsedOutputValidator({ + outputValidator({ paths: [simplePath, longSimplePath, doubleFocalPath, almostDoubleFocalPath], focalIndices: [2, 6, 2, 4], }); @@ -99,6 +99,6 @@ describe('parse payload', () => { it('throws an error if a path lacks the focalPathElem', () => { const { simplePath, noFocalPath, doubleFocalPath, focalPathElem } = testResources; - expect(() => parsePayload([simplePath, noFocalPath, doubleFocalPath], focalPathElem)).toThrowError(); + expect(() => transformDdgData([simplePath, noFocalPath, doubleFocalPath], focalPathElem)).toThrowError(); }); }); diff --git a/packages/jaeger-ui/src/model/ddg/parse-payload.test.resources.js b/packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.resources.js similarity index 100% rename from packages/jaeger-ui/src/model/ddg/parse-payload.test.resources.js rename to packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.resources.js diff --git a/packages/jaeger-ui/src/model/ddg/parse-payload.tsx b/packages/jaeger-ui/src/model/ddg/transform-ddg-data.tsx similarity index 97% rename from packages/jaeger-ui/src/model/ddg/parse-payload.tsx rename to packages/jaeger-ui/src/model/ddg/transform-ddg-data.tsx index 884b5a456c..b4379add6d 100644 --- a/packages/jaeger-ui/src/model/ddg/parse-payload.tsx +++ b/packages/jaeger-ui/src/model/ddg/transform-ddg-data.tsx @@ -19,14 +19,14 @@ import { TDdgPath, TDdgServiceMap, TDdgPathElemsByDistance, - TDdgParsedPayload, + TDdgTransformedDdgData, PathElem, } from './types'; -export default function parsePayload( +export default function transformDdgData( payload: TDdgPayload, { service: focalService, operation: focalOperation }: { service: string; operation?: string } -): TDdgParsedPayload { +): TDdgTransformedDdgData { const serviceMap: TDdgServiceMap = new Map(); const pathElemsByDistance: TDdgPathElemsByDistance = new Map(); diff --git a/packages/jaeger-ui/src/model/ddg/types/index.test.js b/packages/jaeger-ui/src/model/ddg/types.test.js similarity index 98% rename from packages/jaeger-ui/src/model/ddg/types/index.test.js rename to packages/jaeger-ui/src/model/ddg/types.test.js index 741cde22a9..4596a715cf 100644 --- a/packages/jaeger-ui/src/model/ddg/types/index.test.js +++ b/packages/jaeger-ui/src/model/ddg/types.test.js @@ -12,17 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { PathElem } from '.'; +import { PathElem } from './types'; describe('ddg classes', () => { describe('PathElem', () => { + const preexistingPathElems = ['fillerPathElem0', 'fillerPathElem1']; + const testOperation = {}; const testPath = { focalIdx: 5, }; - const testOperation = {}; const testPathIdx = 3; const testVisibilityIdx = 105; - const preexistingPathElems = ['fillerPathElem0', 'fillerPathElem1']; let pathElem; beforeEach(() => { diff --git a/packages/jaeger-ui/src/model/ddg/types/index.tsx b/packages/jaeger-ui/src/model/ddg/types.tsx similarity index 98% rename from packages/jaeger-ui/src/model/ddg/types/index.tsx rename to packages/jaeger-ui/src/model/ddg/types.tsx index 0a5121fac8..eb0cef0fbd 100644 --- a/packages/jaeger-ui/src/model/ddg/types/index.tsx +++ b/packages/jaeger-ui/src/model/ddg/types.tsx @@ -37,9 +37,9 @@ export type TDdgServiceMap = Map; export type TDdgPathElemsByDistance = Map; -export type TDdgParsedPayload = { - paths: TDdgPath[]; +export type TDdgTransformedDdgData = { pathElemsByDistance: TDdgPathElemsByDistance; + paths: TDdgPath[]; services: TDdgServiceMap; }; @@ -53,6 +53,7 @@ export class PathElem { this.memberOf = path; this.operation = operation; this.pathIdx = pathIdx; + operation.pathElems.push(this); } From 25ce34d2b3a26144007969c22321923768f870f6 Mon Sep 17 00:00:00 2001 From: Everett Ross Date: Wed, 15 May 2019 13:17:13 -0400 Subject: [PATCH 10/13] Improve TS usage, partially decouple PathElem Signed-off-by: Everett Ross --- .../jaeger-ui/src/model/ddg/PathElem.test.js | 55 ++++++++++++++++ packages/jaeger-ui/src/model/ddg/PathElem.tsx | 55 ++++++++++++++++ .../src/model/ddg/transform-ddg-data.test.js | 9 ++- .../src/model/ddg/transform-ddg-data.tsx | 58 ++++++++--------- .../jaeger-ui/src/model/ddg/types.test.js | 63 ------------------- packages/jaeger-ui/src/model/ddg/types.tsx | 40 ++---------- 6 files changed, 151 insertions(+), 129 deletions(-) create mode 100644 packages/jaeger-ui/src/model/ddg/PathElem.test.js create mode 100644 packages/jaeger-ui/src/model/ddg/PathElem.tsx delete mode 100644 packages/jaeger-ui/src/model/ddg/types.test.js diff --git a/packages/jaeger-ui/src/model/ddg/PathElem.test.js b/packages/jaeger-ui/src/model/ddg/PathElem.test.js new file mode 100644 index 0000000000..f09214bfb1 --- /dev/null +++ b/packages/jaeger-ui/src/model/ddg/PathElem.test.js @@ -0,0 +1,55 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import PathElem from './PathElem'; + +describe('PathElem', () => { + const testOperation = {}; + const testPath = { + focalIdx: 5, + }; + const testMemberIdx = 3; + const testVisibilityIdx = 105; + let pathElem; + + beforeEach(() => { + pathElem = new PathElem({ path: testPath, operation: testOperation, memberIdx: testMemberIdx }); + }); + + it('initializes instance properties', () => { + expect(pathElem.memberOf).toBe(testPath); + expect(pathElem.operation).toBe(testOperation); + expect(pathElem.memberIdx).toBe(testMemberIdx); + }); + + it('calculates distance', () => { + expect(pathElem.distance).toBe(-2); + }); + + it('sets visibilityIdx', () => { + pathElem.visibilityIdx = testVisibilityIdx; + expect(pathElem.visibilityIdx).toBe(testVisibilityIdx); + }); + + it('errors when trying to access unset visibilityIdx', () => { + expect(() => pathElem.visibilityIdx).toThrowError(); + }); + + it('errors when trying to override visibilityIdx', () => { + pathElem.visibilityIdx = testVisibilityIdx; + expect(() => { + pathElem.visibilityIdx = testVisibilityIdx; + }).toThrowError(); + }); +}); diff --git a/packages/jaeger-ui/src/model/ddg/PathElem.tsx b/packages/jaeger-ui/src/model/ddg/PathElem.tsx new file mode 100644 index 0000000000..56e18c4786 --- /dev/null +++ b/packages/jaeger-ui/src/model/ddg/PathElem.tsx @@ -0,0 +1,55 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { TDdgOperation, TDdgPath } from './types'; + +export default class PathElem { + memberIdx: number; + memberOf: TDdgPath; + operation: TDdgOperation; + private _visibilityIdx?: number; + + constructor({ + path, + operation, + memberIdx, + }: { + path: TDdgPath; + operation: TDdgOperation; + memberIdx: number; + }) { + this.memberIdx = memberIdx; + this.memberOf = path; + this.operation = operation; + } + + get distance() { + return this.memberIdx - this.memberOf.focalIdx; + } + + set visibilityIdx(visibilityIdx: number) { + if (this._visibilityIdx == null) { + this._visibilityIdx = visibilityIdx; + } else { + throw new Error('Visibility Index cannot be changed once set'); + } + } + + get visibilityIdx(): number { + if (this._visibilityIdx == null) { + throw new Error('Visibility Index was never set for this PathElem'); + } + return this._visibilityIdx; + } +} diff --git a/packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.js b/packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.js index 4067357844..8d953e42cd 100644 --- a/packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.js +++ b/packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.js @@ -25,6 +25,7 @@ describe('transform ddg data', () => { const focalPathElemArgument = ignoreFocalOperation ? { service: focalPathElem.service } : focalPathElem; const { paths, services } = transformDdgData(payload, focalPathElemArgument); + // Validate all services and operations are captured expect(new Set(services.keys())).toEqual(new Set(_map(_flatten(payload), 'service'))); services.forEach((service, serviceName) => { expect(new Set(service.operations.keys())).toEqual( @@ -35,22 +36,24 @@ describe('transform ddg data', () => { const expectedVisibilityIndices = []; const visibilityIndicesToDistance = new Map(); + // Validate every pathElem has the correct data paths.forEach((path, pathResultIndex) => { expect(path.focalIdx).toBe(focalIndices[pathResultIndex]); path.members.forEach((member, memberResultIndex) => { - const { distance, memberOf, operation, pathIdx, visibilityIdx } = member; - expect(distance).toBe(pathIdx - focalIndices[pathResultIndex]); + const { distance, memberOf, operation, memberIdx, visibilityIdx } = member; + expect(distance).toBe(memberIdx - focalIndices[pathResultIndex]); expect(memberOf).toBe(path); expect(operation.name).toBe(payload[pathResultIndex][memberResultIndex].operation); expect(operation.pathElems.includes(member)).toBe(true); expect(operation.service.name).toBe(payload[pathResultIndex][memberResultIndex].service); - expect(pathIdx).toBe(memberResultIndex); + expect(memberIdx).toBe(memberResultIndex); expectedVisibilityIndices.push(expectedVisibilityIndices.length); visibilityIndicesToDistance.set(visibilityIdx, distance); }); }); + // Validate that visibility indices are concentric const orderedVisibilityIndices = Array.from(visibilityIndicesToDistance.keys()).sort((a, b) => a - b); expect(orderedVisibilityIndices).toEqual(expectedVisibilityIndices); let distance = 0; diff --git a/packages/jaeger-ui/src/model/ddg/transform-ddg-data.tsx b/packages/jaeger-ui/src/model/ddg/transform-ddg-data.tsx index b4379add6d..c06a62070c 100644 --- a/packages/jaeger-ui/src/model/ddg/transform-ddg-data.tsx +++ b/packages/jaeger-ui/src/model/ddg/transform-ddg-data.tsx @@ -12,64 +12,65 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { - TDdgPayload, - TDdgService, - TDdgOperation, - TDdgPath, - TDdgServiceMap, - TDdgPathElemsByDistance, - TDdgTransformedDdgData, - PathElem, -} from './types'; +import { PathElem, TDdgModel, TDdgPayload, TDdgPath, TDdgPathElemsByDistance, TDdgServiceMap } from './types'; export default function transformDdgData( payload: TDdgPayload, { service: focalService, operation: focalOperation }: { service: string; operation?: string } -): TDdgTransformedDdgData { +): TDdgModel { const serviceMap: TDdgServiceMap = new Map(); const pathElemsByDistance: TDdgPathElemsByDistance = new Map(); const paths = payload.map(payloadPath => { - const path = {} as TDdgPath; + // Path with stand-in values is necessary for assigning PathElem.memberOf + const path: TDdgPath = { focalIdx: -1, members: [] }; - const members = payloadPath.map(({ operation: operationName, service: serviceName }, i) => { - if (!serviceMap.has(serviceName)) { - serviceMap.set(serviceName, { + path.members = payloadPath.map(({ operation: operationName, service: serviceName }, i) => { + // Ensure pathElem.service exists, else create it + let service = serviceMap.get(serviceName); + if (!service) { + service = { name: serviceName, operations: new Map(), - }); + }; + serviceMap.set(serviceName, service); } - const service = serviceMap.get(serviceName) as TDdgService; - if (!service.operations.has(operationName)) { - service.operations.set(operationName, { + // Ensure service has operation, else add it + let operation = service.operations.get(operationName); + if (!operation) { + operation = { name: operationName, service, pathElems: [], - }); + }; + service.operations.set(operationName, operation); } - const operation = service.operations.get(operationName) as TDdgOperation; + // Set focalIdx to first occurrence of focalNode if ( - path.focalIdx == null && + path.focalIdx === -1 && serviceName === focalService && (focalOperation == null || operationName === focalOperation) ) { path.focalIdx = i; } - return new PathElem({ path, operation, pathIdx: i }); + const pathElem = new PathElem({ path, operation, memberIdx: i }); + operation.pathElems.push(pathElem); + return pathElem; }); - if (path.focalIdx == null) { + if (path.focalIdx === -1) { throw new Error('A payload path lacked the focalNode'); } - path.members = members; - members.forEach(member => { - if (pathElemsByDistance.has(member.distance)) { - (pathElemsByDistance.get(member.distance) as PathElem[]).push(member); + // Track all pathElems by their distance for visibilityIdx assignment and hop management + // This needs to be a separate loop as path.focalIdx must be set before distance can be calculated + path.members.forEach(member => { + const pathElemsAtDistance = pathElemsByDistance.get(member.distance); + if (pathElemsAtDistance) { + pathElemsAtDistance.push(member); } else { pathElemsByDistance.set(member.distance, [member]); } @@ -78,6 +79,7 @@ export default function transformDdgData( return path; }); + // Assign visibility indices such there is a positive, dependent correlation between visibilityIdx and distance let upstream = 1; let downstream = 0; let visibilityIdx = 0; diff --git a/packages/jaeger-ui/src/model/ddg/types.test.js b/packages/jaeger-ui/src/model/ddg/types.test.js deleted file mode 100644 index 4596a715cf..0000000000 --- a/packages/jaeger-ui/src/model/ddg/types.test.js +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2019 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { PathElem } from './types'; - -describe('ddg classes', () => { - describe('PathElem', () => { - const preexistingPathElems = ['fillerPathElem0', 'fillerPathElem1']; - const testOperation = {}; - const testPath = { - focalIdx: 5, - }; - const testPathIdx = 3; - const testVisibilityIdx = 105; - let pathElem; - - beforeEach(() => { - testOperation.pathElems = preexistingPathElems.slice(); - pathElem = new PathElem({ path: testPath, operation: testOperation, pathIdx: testPathIdx }); - }); - - it('initializes instance properties', () => { - expect(pathElem.memberOf).toBe(testPath); - expect(pathElem.operation).toBe(testOperation); - expect(pathElem.pathIdx).toBe(testPathIdx); - }); - - it('adds itself to provided operation', () => { - expect(testOperation.pathElems).toEqual([...preexistingPathElems, pathElem]); - }); - - it('calculates distance', () => { - expect(pathElem.distance).toBe(-2); - }); - - it('sets visibilityIdx', () => { - pathElem.visibilityIdx = testVisibilityIdx; - expect(pathElem.visibilityIdx).toBe(testVisibilityIdx); - }); - - it('errors when trying to access unset visibilityIdx', () => { - expect(() => pathElem.visibilityIdx).toThrowError(); - }); - - it('errors when trying to override visibilityIdx', () => { - pathElem.visibilityIdx = testVisibilityIdx; - expect(() => { - pathElem.visibilityIdx = testVisibilityIdx; - }).toThrowError(); - }); - }); -}); diff --git a/packages/jaeger-ui/src/model/ddg/types.tsx b/packages/jaeger-ui/src/model/ddg/types.tsx index eb0cef0fbd..22e9c71e94 100644 --- a/packages/jaeger-ui/src/model/ddg/types.tsx +++ b/packages/jaeger-ui/src/model/ddg/types.tsx @@ -12,6 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +import PathElem from './PathElem'; + +export { default as PathElem } from './PathElem'; + export type TDdgPayload = { operation: string; service: string; @@ -37,42 +41,8 @@ export type TDdgServiceMap = Map; export type TDdgPathElemsByDistance = Map; -export type TDdgTransformedDdgData = { +export type TDdgModel = { pathElemsByDistance: TDdgPathElemsByDistance; paths: TDdgPath[]; services: TDdgServiceMap; }; - -export class PathElem { - memberOf: TDdgPath; - operation: TDdgOperation; - pathIdx: number; - private _visibilityIdx?: number; - - constructor({ path, operation, pathIdx }: { path: TDdgPath; operation: TDdgOperation; pathIdx: number }) { - this.memberOf = path; - this.operation = operation; - this.pathIdx = pathIdx; - - operation.pathElems.push(this); - } - - get distance() { - return this.pathIdx - this.memberOf.focalIdx; - } - - set visibilityIdx(visibiliityIdx: number) { - if (this._visibilityIdx == null) { - this._visibilityIdx = visibiliityIdx; - } else { - throw new Error('Visibility Index cannot be changed once set'); - } - } - - get visibilityIdx(): number { - if (this._visibilityIdx == null) { - throw new Error('Visibility Index was never set for this PathElem'); - } - return this._visibilityIdx; - } -} From 3fd232fee8b0e3792c68d5a42d8da8f9985ffaf3 Mon Sep 17 00:00:00 2001 From: Everett Ross Date: Thu, 16 May 2019 14:20:18 -0400 Subject: [PATCH 11/13] Avoid typecasting, ensure stable visibilityIndices Signed-off-by: Everett Ross --- .../src/model/ddg/transform-ddg-data.test.js | 58 ++++++- .../src/model/ddg/transform-ddg-data.tsx | 146 ++++++++++-------- packages/jaeger-ui/src/model/ddg/types.tsx | 8 +- 3 files changed, 143 insertions(+), 69 deletions(-) diff --git a/packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.js b/packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.js index 8d953e42cd..45e8c34f4f 100644 --- a/packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.js +++ b/packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.js @@ -23,7 +23,7 @@ describe('transform ddg data', () => { function outputValidator({ paths: payload, focalIndices, ignoreFocalOperation = false }) { const { focalPathElem } = testResources; const focalPathElemArgument = ignoreFocalOperation ? { service: focalPathElem.service } : focalPathElem; - const { paths, services } = transformDdgData(payload, focalPathElemArgument); + const { paths, services, visibilityIdxToPathElem } = transformDdgData(payload, focalPathElemArgument); // Validate all services and operations are captured expect(new Set(services.keys())).toEqual(new Set(_map(_flatten(payload), 'service'))); @@ -64,6 +64,8 @@ describe('transform ddg data', () => { } else if (currentDistance > distance) { distance = currentDistance; } + + expect(visibilityIdxToPathElem.get(orderedIdx).visibilityIdx).toBe(orderedIdx); }); } @@ -95,11 +97,61 @@ describe('transform ddg data', () => { it('transforms a payload with significant overlap between paths', () => { const { simplePath, longSimplePath, doubleFocalPath, almostDoubleFocalPath } = testResources; outputValidator({ - paths: [simplePath, longSimplePath, doubleFocalPath, almostDoubleFocalPath], - focalIndices: [2, 6, 2, 4], + paths: [simplePath, doubleFocalPath, almostDoubleFocalPath, longSimplePath], + focalIndices: [2, 2, 4, 6], }); }); + it('sorts payload paths to ensure stable visibilityIndices', () => { + const { + focalPathElem, + simplePath, + longSimplePath, + doubleFocalPath, + almostDoubleFocalPath, + } = testResources; + const { visibilityIdxToPathElem: presortedPathsVisibilityIdxToPathElemMap } = transformDdgData( + [simplePath, doubleFocalPath, almostDoubleFocalPath, longSimplePath], + focalPathElem + ); + const { visibilityIdxToPathElem: unsortedPathsVisibilityIdxToPathElemMap } = transformDdgData( + [longSimplePath, almostDoubleFocalPath, simplePath, doubleFocalPath], + focalPathElem + ); + + expect(Array.from(presortedPathsVisibilityIdxToPathElemMap.keys())).toEqual( + Array.from(unsortedPathsVisibilityIdxToPathElemMap.keys()) + ); + presortedPathsVisibilityIdxToPathElemMap.forEach( + (presortedPathsPathElem, presortedPathsVisibilityIdx) => { + const { + memberIdx: presortedPathsMemberIdx, + memberOf: presortedPathsMemberOf, + operation: presortedPathsOperation, + } = presortedPathsPathElem; + const { focalIdx: presortedPathsFocalIdx } = presortedPathsMemberOf; + const { name: presortedPathsOperationName, service: presortedService } = presortedPathsOperation; + const { name: presortedPathsServiceName } = presortedService; + + const { + memberIdx: unsortedPathsMemberIdx, + memberOf: unsortedPathsMemberOf, + operation: unsortedPathsOperation, + visibilityIdx: unsortedPathsVisibilityIdx, + } = unsortedPathsVisibilityIdxToPathElemMap.get(presortedPathsVisibilityIdx); + const { focalIdx: unsortedPathsFocalIdx } = unsortedPathsMemberOf; + const { name: unsortedPathsOperationName, service: unsortedService } = unsortedPathsOperation; + const { name: unsortedPathsServiceName } = unsortedService; + + expect(unsortedPathsMemberIdx).toBe(presortedPathsMemberIdx); + expect(unsortedPathsFocalIdx).toBe(presortedPathsFocalIdx); + expect(unsortedPathsOperationName).toBe(presortedPathsOperationName); + expect(unsortedPathsServiceName).toBe(presortedPathsServiceName); + expect(unsortedPathsVisibilityIdx).toBe(presortedPathsVisibilityIdx); + } + ); + }); + it('throws an error if a path lacks the focalPathElem', () => { const { simplePath, noFocalPath, doubleFocalPath, focalPathElem } = testResources; expect(() => transformDdgData([simplePath, noFocalPath, doubleFocalPath], focalPathElem)).toThrowError(); diff --git a/packages/jaeger-ui/src/model/ddg/transform-ddg-data.tsx b/packages/jaeger-ui/src/model/ddg/transform-ddg-data.tsx index c06a62070c..3194b3f9d8 100644 --- a/packages/jaeger-ui/src/model/ddg/transform-ddg-data.tsx +++ b/packages/jaeger-ui/src/model/ddg/transform-ddg-data.tsx @@ -12,7 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { PathElem, TDdgModel, TDdgPayload, TDdgPath, TDdgPathElemsByDistance, TDdgServiceMap } from './types'; +import { + PathElem, + TDdgModel, + TDdgPayload, + TDdgPayloadEntry, + TDdgPath, + TDdgPathElemsByDistance, + TDdgServiceMap, + TDdgVisibilityIdxToPathElem, +} from './types'; + +const stringifyPayloadEntry = ({ service, operation }: TDdgPayloadEntry) => `${service}::${operation}`; export default function transformDdgData( payload: TDdgPayload, @@ -21,87 +32,94 @@ export default function transformDdgData( const serviceMap: TDdgServiceMap = new Map(); const pathElemsByDistance: TDdgPathElemsByDistance = new Map(); - const paths = payload.map(payloadPath => { - // Path with stand-in values is necessary for assigning PathElem.memberOf - const path: TDdgPath = { focalIdx: -1, members: [] }; - - path.members = payloadPath.map(({ operation: operationName, service: serviceName }, i) => { - // Ensure pathElem.service exists, else create it - let service = serviceMap.get(serviceName); - if (!service) { - service = { - name: serviceName, - operations: new Map(), - }; - serviceMap.set(serviceName, service); - } + const paths = payload + .slice() + .sort((a, b) => + a + .map(stringifyPayloadEntry) + .join() + .localeCompare(b.map(stringifyPayloadEntry).join()) + ) + .map(payloadPath => { + // Path with stand-in values is necessary for assigning PathElem.memberOf + const path: TDdgPath = { focalIdx: -1, members: [] }; - // Ensure service has operation, else add it - let operation = service.operations.get(operationName); - if (!operation) { - operation = { - name: operationName, - service, - pathElems: [], - }; - service.operations.set(operationName, operation); - } + path.members = payloadPath.map(({ operation: operationName, service: serviceName }, i) => { + // Ensure pathElem.service exists, else create it + let service = serviceMap.get(serviceName); + if (!service) { + service = { + name: serviceName, + operations: new Map(), + }; + serviceMap.set(serviceName, service); + } - // Set focalIdx to first occurrence of focalNode - if ( - path.focalIdx === -1 && - serviceName === focalService && - (focalOperation == null || operationName === focalOperation) - ) { - path.focalIdx = i; - } + // Ensure service has operation, else add it + let operation = service.operations.get(operationName); + if (!operation) { + operation = { + name: operationName, + service, + pathElems: [], + }; + service.operations.set(operationName, operation); + } - const pathElem = new PathElem({ path, operation, memberIdx: i }); - operation.pathElems.push(pathElem); - return pathElem; - }); + // Set focalIdx to first occurrence of focalNode + if ( + path.focalIdx === -1 && + serviceName === focalService && + (focalOperation == null || operationName === focalOperation) + ) { + path.focalIdx = i; + } - if (path.focalIdx === -1) { - throw new Error('A payload path lacked the focalNode'); - } + const pathElem = new PathElem({ path, operation, memberIdx: i }); + operation.pathElems.push(pathElem); + return pathElem; + }); - // Track all pathElems by their distance for visibilityIdx assignment and hop management - // This needs to be a separate loop as path.focalIdx must be set before distance can be calculated - path.members.forEach(member => { - const pathElemsAtDistance = pathElemsByDistance.get(member.distance); - if (pathElemsAtDistance) { - pathElemsAtDistance.push(member); - } else { - pathElemsByDistance.set(member.distance, [member]); + if (path.focalIdx === -1) { + throw new Error('A payload path lacked the focalNode'); } - }); - return path; - }); + // Track all pathElems by their distance for visibilityIdx assignment and hop management + // This needs to be a separate loop as path.focalIdx must be set before distance can be calculated + path.members.forEach(member => { + const pathElemsAtDistance = pathElemsByDistance.get(member.distance); + if (pathElemsAtDistance) { + pathElemsAtDistance.push(member); + } else { + pathElemsByDistance.set(member.distance, [member]); + } + }); + + return path; + }); // Assign visibility indices such there is a positive, dependent correlation between visibilityIdx and distance - let upstream = 1; let downstream = 0; + let downstreamPathElems: PathElem[] | void; + let upstream = 1; + let upstreamPathElems: PathElem[] | void; let visibilityIdx = 0; + const visibilityIdxToPathElem: TDdgVisibilityIdxToPathElem = new Map(); function setPathElemVisibilityIdx(pathElem: PathElem) { + visibilityIdxToPathElem.set(visibilityIdx, pathElem); pathElem.visibilityIdx = visibilityIdx++; // eslint-disable-line no-param-reassign } - while (pathElemsByDistance.has(upstream) || pathElemsByDistance.has(downstream)) { - let nextArrayToIndex: PathElem[]; - if ( - (Math.abs(downstream) < upstream && pathElemsByDistance.has(downstream)) || - !pathElemsByDistance.has(upstream) - ) { - nextArrayToIndex = pathElemsByDistance.get(downstream--) as PathElem[]; - } else { - nextArrayToIndex = pathElemsByDistance.get(upstream++) as PathElem[]; - } - nextArrayToIndex.forEach(setPathElemVisibilityIdx); - } + do { + downstreamPathElems = pathElemsByDistance.get(downstream--); + upstreamPathElems = pathElemsByDistance.get(upstream++); + if (downstreamPathElems) downstreamPathElems.forEach(setPathElemVisibilityIdx); + if (upstreamPathElems) upstreamPathElems.forEach(setPathElemVisibilityIdx); + } while (downstreamPathElems || upstreamPathElems); return { paths, pathElemsByDistance, services: serviceMap, + visibilityIdxToPathElem, }; } diff --git a/packages/jaeger-ui/src/model/ddg/types.tsx b/packages/jaeger-ui/src/model/ddg/types.tsx index 22e9c71e94..ef035d04b1 100644 --- a/packages/jaeger-ui/src/model/ddg/types.tsx +++ b/packages/jaeger-ui/src/model/ddg/types.tsx @@ -16,10 +16,12 @@ import PathElem from './PathElem'; export { default as PathElem } from './PathElem'; -export type TDdgPayload = { +export type TDdgPayloadEntry = { operation: string; service: string; -}[][]; +}; + +export type TDdgPayload = TDdgPayloadEntry[][]; export type TDdgService = { name: string; @@ -40,9 +42,11 @@ export type TDdgPath = { export type TDdgServiceMap = Map; export type TDdgPathElemsByDistance = Map; +export type TDdgVisibilityIdxToPathElem = Map; export type TDdgModel = { pathElemsByDistance: TDdgPathElemsByDistance; paths: TDdgPath[]; services: TDdgServiceMap; + visibilityIdxToPathElem: TDdgVisibilityIdxToPathElem; }; From b821203208ea5aa854b125a5c480bcbd7cfc4cb4 Mon Sep 17 00:00:00 2001 From: Everett Ross Date: Thu, 16 May 2019 14:27:34 -0400 Subject: [PATCH 12/13] Rename new files Signed-off-by: Everett Ross --- .../{transform-ddg-data.test.js => transformDdgData.test.js} | 4 ++-- ...a.test.resources.js => transformDdgData.test.resources.js} | 0 .../ddg/{transform-ddg-data.tsx => transformDdgData.tsx} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/jaeger-ui/src/model/ddg/{transform-ddg-data.test.js => transformDdgData.test.js} (98%) rename packages/jaeger-ui/src/model/ddg/{transform-ddg-data.test.resources.js => transformDdgData.test.resources.js} (100%) rename packages/jaeger-ui/src/model/ddg/{transform-ddg-data.tsx => transformDdgData.tsx} (100%) diff --git a/packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.js b/packages/jaeger-ui/src/model/ddg/transformDdgData.test.js similarity index 98% rename from packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.js rename to packages/jaeger-ui/src/model/ddg/transformDdgData.test.js index 45e8c34f4f..dd61af559c 100644 --- a/packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.js +++ b/packages/jaeger-ui/src/model/ddg/transformDdgData.test.js @@ -16,8 +16,8 @@ import _filter from 'lodash/filter'; import _flatten from 'lodash/flatten'; import _map from 'lodash/map'; -import transformDdgData from './transform-ddg-data'; -import * as testResources from './transform-ddg-data.test.resources'; +import transformDdgData from './transformDdgData'; +import * as testResources from './transformDdgData.test.resources'; describe('transform ddg data', () => { function outputValidator({ paths: payload, focalIndices, ignoreFocalOperation = false }) { diff --git a/packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.resources.js b/packages/jaeger-ui/src/model/ddg/transformDdgData.test.resources.js similarity index 100% rename from packages/jaeger-ui/src/model/ddg/transform-ddg-data.test.resources.js rename to packages/jaeger-ui/src/model/ddg/transformDdgData.test.resources.js diff --git a/packages/jaeger-ui/src/model/ddg/transform-ddg-data.tsx b/packages/jaeger-ui/src/model/ddg/transformDdgData.tsx similarity index 100% rename from packages/jaeger-ui/src/model/ddg/transform-ddg-data.tsx rename to packages/jaeger-ui/src/model/ddg/transformDdgData.tsx From 54577cc241f86218dbace7c379d281f3bc98945b Mon Sep 17 00:00:00 2001 From: Everett Ross Date: Fri, 24 May 2019 12:10:45 -0400 Subject: [PATCH 13/13] Improve payload sorting efficiency Signed-off-by: Everett Ross --- .../src/model/ddg/transformDdgData.tsx | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/jaeger-ui/src/model/ddg/transformDdgData.tsx b/packages/jaeger-ui/src/model/ddg/transformDdgData.tsx index 3194b3f9d8..55e7533d7a 100644 --- a/packages/jaeger-ui/src/model/ddg/transformDdgData.tsx +++ b/packages/jaeger-ui/src/model/ddg/transformDdgData.tsx @@ -23,7 +23,7 @@ import { TDdgVisibilityIdxToPathElem, } from './types'; -const stringifyPayloadEntry = ({ service, operation }: TDdgPayloadEntry) => `${service}::${operation}`; +const stringifyPayloadEntry = ({ service, operation }: TDdgPayloadEntry) => `${service}\v${operation}`; export default function transformDdgData( payload: TDdgPayload, @@ -31,15 +31,25 @@ export default function transformDdgData( ): TDdgModel { const serviceMap: TDdgServiceMap = new Map(); const pathElemsByDistance: TDdgPathElemsByDistance = new Map(); + const pathsComparisonMap: Map = new Map(); const paths = payload .slice() - .sort((a, b) => - a - .map(stringifyPayloadEntry) - .join() - .localeCompare(b.map(stringifyPayloadEntry).join()) - ) + .sort((a, b) => { + let aCompareValue = pathsComparisonMap.get(a); + if (!aCompareValue) { + aCompareValue = a.map(stringifyPayloadEntry).join(); + pathsComparisonMap.set(a, aCompareValue); + } + let bCompareValue = pathsComparisonMap.get(b); + if (!bCompareValue) { + bCompareValue = b.map(stringifyPayloadEntry).join(); + pathsComparisonMap.set(b, bCompareValue); + } + if (aCompareValue > bCompareValue) return 1; + if (aCompareValue < bCompareValue) return -1; + return 0; + }) .map(payloadPath => { // Path with stand-in values is necessary for assigning PathElem.memberOf const path: TDdgPath = { focalIdx: -1, members: [] };