transforms.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import { identity } from 'lodash';
  2. import { DataFrame, FieldType, MutableDataFrame, TraceKeyValuePair, TraceLog, TraceSpanRow } from '@grafana/data';
  3. import { ZipkinAnnotation, ZipkinEndpoint, ZipkinSpan } from '../types';
  4. /**
  5. * Transforms response to Grafana trace data frame.
  6. */
  7. export function transformResponse(zSpans: ZipkinSpan[]): DataFrame {
  8. const spanRows = zSpans.map(transformSpan);
  9. const frame = new MutableDataFrame({
  10. fields: [
  11. { name: 'traceID', type: FieldType.string },
  12. { name: 'spanID', type: FieldType.string },
  13. { name: 'parentSpanID', type: FieldType.string },
  14. { name: 'operationName', type: FieldType.string },
  15. { name: 'serviceName', type: FieldType.string },
  16. { name: 'serviceTags', type: FieldType.other },
  17. { name: 'startTime', type: FieldType.number },
  18. { name: 'duration', type: FieldType.number },
  19. { name: 'logs', type: FieldType.other },
  20. { name: 'tags', type: FieldType.other },
  21. ],
  22. meta: {
  23. preferredVisualisationType: 'trace',
  24. custom: {
  25. traceFormat: 'zipkin',
  26. },
  27. },
  28. });
  29. for (const span of spanRows) {
  30. frame.add(span);
  31. }
  32. return frame;
  33. }
  34. function transformSpan(span: ZipkinSpan): TraceSpanRow {
  35. const row = {
  36. traceID: span.traceId,
  37. spanID: span.id,
  38. parentSpanID: span.parentId,
  39. operationName: span.name,
  40. serviceName: span.localEndpoint?.serviceName || span.remoteEndpoint?.serviceName || 'unknown',
  41. serviceTags: serviceTags(span),
  42. startTime: span.timestamp / 1000,
  43. duration: span.duration / 1000,
  44. logs: span.annotations?.map(transformAnnotation) ?? [],
  45. tags: Object.keys(span.tags || {}).reduce<TraceKeyValuePair[]>((acc, key) => {
  46. // If tag is error we remap it to simple boolean so that the trace ui will show an error icon.
  47. if (key === 'error') {
  48. acc.push({
  49. key: 'error',
  50. value: true,
  51. });
  52. acc.push({
  53. key: 'errorValue',
  54. value: span.tags!['error'],
  55. });
  56. return acc;
  57. }
  58. acc.push({ key, value: span.tags![key] });
  59. return acc;
  60. }, []),
  61. };
  62. if (span.kind) {
  63. row.tags = [
  64. {
  65. key: 'kind',
  66. value: span.kind,
  67. },
  68. ...(row.tags ?? []),
  69. ];
  70. }
  71. if (span.shared) {
  72. row.tags = [
  73. {
  74. key: 'shared',
  75. value: span.shared,
  76. },
  77. ...(row.tags ?? []),
  78. ];
  79. }
  80. return row;
  81. }
  82. /**
  83. * Maps annotations as a log as that seems to be the closest thing.
  84. * See https://zipkin.io/zipkin-api/#/default/get_trace__traceId_
  85. */
  86. function transformAnnotation(annotation: ZipkinAnnotation): TraceLog {
  87. return {
  88. timestamp: annotation.timestamp,
  89. fields: [
  90. {
  91. key: 'annotation',
  92. value: annotation.value,
  93. },
  94. ],
  95. };
  96. }
  97. function serviceTags(span: ZipkinSpan): TraceKeyValuePair[] {
  98. const endpoint = span.localEndpoint || span.remoteEndpoint;
  99. if (!endpoint) {
  100. return [];
  101. }
  102. return [
  103. valueToTag('ipv4', endpoint.ipv4),
  104. valueToTag('ipv6', endpoint.ipv6),
  105. valueToTag('port', endpoint.port),
  106. valueToTag('endpointType', span.localEndpoint ? 'local' : 'remote'),
  107. ].filter(identity) as TraceKeyValuePair[];
  108. }
  109. function valueToTag<T>(key: string, value: T): TraceKeyValuePair<T> | undefined {
  110. if (!value) {
  111. return undefined;
  112. }
  113. return {
  114. key,
  115. value,
  116. };
  117. }
  118. /**
  119. * Transforms data frame to Zipkin response
  120. */
  121. export const transformToZipkin = (data: MutableDataFrame): ZipkinSpan[] => {
  122. let response: ZipkinSpan[] = [];
  123. for (let i = 0; i < data.length; i++) {
  124. const span = data.get(i);
  125. response.push({
  126. traceId: span.traceID,
  127. parentId: span.parentSpanID,
  128. name: span.operationName,
  129. id: span.spanID,
  130. timestamp: span.startTime * 1000,
  131. duration: span.duration * 1000,
  132. ...getEndpoint(span),
  133. annotations: span.logs.length
  134. ? span.logs.map((l: TraceLog) => ({ timestamp: l.timestamp, value: l.fields[0].value }))
  135. : undefined,
  136. tags: span.tags.length
  137. ? span.tags
  138. .filter((t: TraceKeyValuePair) => t.key !== 'kind' && t.key !== 'endpointType' && t.key !== 'shared')
  139. .reduce((tags: { [key: string]: string }, t: TraceKeyValuePair) => {
  140. if (t.key === 'error') {
  141. return {
  142. ...tags,
  143. [t.key]: span.tags.find((t: TraceKeyValuePair) => t.key === 'errorValue').value || '',
  144. };
  145. }
  146. return { ...tags, [t.key]: t.value };
  147. }, {})
  148. : undefined,
  149. kind: span.tags.find((t: TraceKeyValuePair) => t.key === 'kind')?.value,
  150. shared: span.tags.find((t: TraceKeyValuePair) => t.key === 'shared')?.value,
  151. });
  152. }
  153. return response;
  154. };
  155. // Returns remote or local endpoint object
  156. const getEndpoint = (span: any): { [key: string]: ZipkinEndpoint } | undefined => {
  157. const key =
  158. span.serviceTags.find((t: TraceKeyValuePair) => t.key === 'endpointType')?.value === 'local'
  159. ? 'localEndpoint'
  160. : 'remoteEndpoint';
  161. return span.serviceName !== 'unknown'
  162. ? {
  163. [key]: {
  164. serviceName: span.serviceName,
  165. ipv4: span.serviceTags.find((t: TraceKeyValuePair) => t.key === 'ipv4')?.value,
  166. ipv6: span.serviceTags.find((t: TraceKeyValuePair) => t.key === 'ipv6')?.value,
  167. port: span.serviceTags.find((t: TraceKeyValuePair) => t.key === 'port')?.value,
  168. },
  169. }
  170. : undefined;
  171. };