generate-summary.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import fs from 'fs';
  2. import path from 'path';
  3. import readline from 'readline';
  4. export type DataPoint = {
  5. type: 'Point',
  6. data: {
  7. time: string;
  8. value: number;
  9. },
  10. metric: string;
  11. };
  12. export type TimeSeriesPoint = { timestamp: number; value: number; };
  13. export type LoadTestSummary = {
  14. timestamp: string;
  15. script: string;
  16. productCount: number;
  17. testDuration: number;
  18. requests: number;
  19. throughput: number;
  20. requestDurationSummary: {
  21. avg: number;
  22. min: number;
  23. max: number;
  24. med: number;
  25. p90: number;
  26. p95: number;
  27. p99: number;
  28. },
  29. requestDurationTimeSeries: TimeSeriesPoint[];
  30. concurrentUsersTimeSeries: TimeSeriesPoint[];
  31. };
  32. if (require.main === module) {
  33. const resultsFile = process.argv[2];
  34. generateSummary(resultsFile)
  35. .then(result => {
  36. // tslint:disable-next-line:no-console
  37. console.log(JSON.stringify(result, null, 2));
  38. process.exit(0);
  39. });
  40. }
  41. /**
  42. * Reads the raw JSON file output from k6 and parses it into a summary object.
  43. */
  44. export async function generateSummary(rawResultsFile: string): Promise<LoadTestSummary> {
  45. const lineReader = readline.createInterface({
  46. input: fs.createReadStream(path.join(__dirname, 'results', rawResultsFile)),
  47. crlfDelay: Infinity,
  48. });
  49. let reqs = 0;
  50. let min = Infinity;
  51. let max = 0;
  52. let sum = 0;
  53. let startTime = 0;
  54. let endTime = 0;
  55. const durations: number[] = [];
  56. const requestDurationTimeSeries: TimeSeriesPoint[] = [];
  57. const concurrentUsersTimeSeries: TimeSeriesPoint[] = [];
  58. return new Promise((resolve, reject) => {
  59. lineReader.on('line', line => {
  60. const row = JSON.parse(line);
  61. if (isDataPoint(row)) {
  62. if (row.metric === 'http_reqs') {
  63. reqs++;
  64. }
  65. if (row.metric === 'http_req_duration') {
  66. const duration = row.data.value;
  67. durations.push(duration);
  68. requestDurationTimeSeries.push({ timestamp: +(new Date(row.data.time)), value: row.data.value });
  69. if (duration > max) {
  70. max = duration;
  71. }
  72. if (duration < min) {
  73. min = duration;
  74. }
  75. sum += duration;
  76. }
  77. if (row.metric === 'vus') {
  78. concurrentUsersTimeSeries.push({ timestamp: +(new Date(row.data.time)), value: row.data.value });
  79. }
  80. if (!startTime) {
  81. startTime = +(new Date(row.data.time));
  82. }
  83. endTime = +(new Date(row.data.time));
  84. }
  85. });
  86. lineReader.on('close', () => {
  87. const duration = (endTime - startTime) / 1000;
  88. durations.sort((a, b) => a - b);
  89. resolve({
  90. timestamp: new Date().toISOString(),
  91. script: rawResultsFile.split('.')[0],
  92. productCount: +(rawResultsFile.split('.')[2]),
  93. testDuration: duration,
  94. requests: reqs,
  95. throughput: reqs / duration,
  96. requestDurationSummary: {
  97. avg: sum / reqs,
  98. min,
  99. max,
  100. med: durations[Math.round(durations.length / 2)],
  101. p90: percentile(90, durations),
  102. p95: percentile(95, durations),
  103. p99: percentile(99, durations),
  104. },
  105. requestDurationTimeSeries,
  106. concurrentUsersTimeSeries,
  107. });
  108. });
  109. });
  110. }
  111. function isDataPoint(row: any): row is DataPoint {
  112. return row && row.type === 'Point';
  113. }
  114. function percentile(p: number, sortedValues: number[]): number {
  115. const ordinalRank = ((p / 100) * sortedValues.length) - 1;
  116. if (Number.isInteger(ordinalRank)) {
  117. return sortedValues[ordinalRank];
  118. }
  119. // if the rank is not an integer, use linear interpolation between the
  120. // surrounding values.
  121. const j = sortedValues[Math.floor(ordinalRank)];
  122. const k = sortedValues[Math.ceil(ordinalRank)];
  123. const f = ordinalRank - Math.floor(ordinalRank);
  124. return j + (k - j) * f;
  125. }