From 1183f0ba90a6c988d9fa8449f59dc320f5e157ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Tue, 6 Aug 2019 10:37:13 +0200 Subject: [PATCH 01/10] Expose recommendations and `check()` API --- index.js | 48 +++++++++++++++++++++++++++++++--------- recommendations/index.js | 1 + 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index 6d977a11..457c5c67 100644 --- a/index.js +++ b/index.js @@ -17,6 +17,7 @@ const SystemInfoDecoder = require('./format/system-info-decoder.js') const TraceEventDecoder = require('./format/trace-event-decoder.js') const ProcessStatDecoder = require('./format/process-stat-decoder.js') const RenderRecommendations = require('./recommendations/index.js') +const { recommendations } = RenderRecommendations const minifyStream = require('minify-stream') const v8 = require('v8') const HEAP_MAX = v8.getHeapStatistics().heap_size_limit @@ -136,15 +137,7 @@ class ClinicDoctor extends events.EventEmitter { }) } - visualize (dataDirname, outputFilename, callback) { - const fakeDataPath = path.join(__dirname, 'visualizer', 'data.json') - const stylePath = path.join(__dirname, 'visualizer', 'style.css') - const scriptPath = path.join(__dirname, 'visualizer', 'main.js') - const logoPath = path.join(__dirname, 'visualizer', 'app-logo.svg') - const nearFormLogoPath = path.join(__dirname, 'visualizer', 'nearform-logo.svg') - const clinicFaviconPath = path.join(__dirname, 'visualizer', 'clinic-favicon.png.b64') - - // Load data + createAnalysis (dataDirname) { const paths = getLoggingPaths({ path: dataDirname }) const systemInfoReader = pumpify.obj( @@ -160,9 +153,44 @@ class ClinicDoctor extends events.EventEmitter { new ProcessStatDecoder() ) + // create analysis + const analysis = new Analysis(traceEventReader, processStatReader) + + return { + traceEventReader, + processStatReader, + analysis + } + } + + check (dataDirname, callback) { + const { analysis } = this.createAnalysis(dataDirname) + analysis + .on('error', callback) + .once('data', (result) => { + result.recommendation = recommendations.find(rec => rec.category === result.issueCategory) + callback(null, result) + }) + } + + visualize (dataDirname, outputFilename, callback) { + const fakeDataPath = path.join(__dirname, 'visualizer', 'data.json') + const stylePath = path.join(__dirname, 'visualizer', 'style.css') + const scriptPath = path.join(__dirname, 'visualizer', 'main.js') + const logoPath = path.join(__dirname, 'visualizer', 'app-logo.svg') + const nearFormLogoPath = path.join(__dirname, 'visualizer', 'nearform-logo.svg') + const clinicFaviconPath = path.join(__dirname, 'visualizer', 'clinic-favicon.png.b64') + + // Load data + const { + traceEventReader, + processStatReader, + analysis + } = this.createAnalysis(dataDirname) + // create analysis const analysisStringified = pumpify( - new Analysis(traceEventReader, processStatReader), + analysis, new stream.Transform({ readableObjectMode: false, writableObjectMode: true, diff --git a/recommendations/index.js b/recommendations/index.js index 55e4aaa2..f6daf530 100644 --- a/recommendations/index.js +++ b/recommendations/index.js @@ -139,3 +139,4 @@ class RenderRecommendations extends stream.Readable { } module.exports = RenderRecommendations +module.exports.recommendations = recommendations From 14b86e844e1ab61805cf2171859f73ded31d25c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Tue, 13 Aug 2019 13:12:48 +0200 Subject: [PATCH 02/10] return recommendation from visualize also --- README.md | 9 +++++++++ index.js | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3f8de8b2..a888bf1a 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,15 @@ directory will be the value in the callback. stdout, stderr, and stdin will be relayed to the calling process. As will the `SIGINT` event. +#### `doctor.check(dataFilename, callback)` + +Will consume the datafile specified by `dataFilename`, this datafile will be +produced by the sampler using `doctor.collect`. + +`doctor.check` will then analyse the data file and call the +`callback(err, result)` with an object containing possible issues and a +recommendation on `result.recommendation`. + #### `doctor.visualize(dataFilename, outputFilename, callback)` Will consume the datafile specified by `dataFilename`, this datafile will be diff --git a/index.js b/index.js index 37afd40c..da31cddb 100644 --- a/index.js +++ b/index.js @@ -25,6 +25,10 @@ const buildJs = require('@nearform/clinic-common/scripts/build-js') const buildCss = require('@nearform/clinic-common/scripts/build-css') const mainTemplate = require('@nearform/clinic-common/templates/main') +function getRecommendation (issueCategory) { + return recommendations.find(rec => rec.category === issueCategory) +} + class ClinicDoctor extends events.EventEmitter { constructor (settings = {}) { super() @@ -168,7 +172,7 @@ class ClinicDoctor extends events.EventEmitter { analysis .on('error', callback) .once('data', (result) => { - result.recommendation = recommendations.find(rec => rec.category === result.issueCategory) + result.recommendation = getRecommendation(result.issueCategory) callback(null, result) }) } @@ -188,6 +192,8 @@ class ClinicDoctor extends events.EventEmitter { analysis } = this.createAnalysis(dataDirname) + let result = null + // create analysis const analysisStringified = pumpify( analysis, @@ -195,6 +201,7 @@ class ClinicDoctor extends events.EventEmitter { readableObjectMode: false, writableObjectMode: true, transform (data, encoding, callback) { + result = data callback(null, JSON.stringify(data)) } }) @@ -302,7 +309,12 @@ class ClinicDoctor extends events.EventEmitter { fs.createWriteStream(outputFilename), function (err) { clearInterval(checkHeapInterval) - callback(err) + if (err) { + callback(err) + } else { + result.recommendation = getRecommendation(result.issueCategory) + callback(null, result) + } } ) } From 731d91387ff2cd7f21a8070665af9ea23da8c929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Tue, 13 Aug 2019 16:46:10 +0200 Subject: [PATCH 03/10] add barebones test --- test/cmd-check.test.js | 103 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 test/cmd-check.test.js diff --git a/test/cmd-check.test.js b/test/cmd-check.test.js new file mode 100644 index 00000000..71c8abf2 --- /dev/null +++ b/test/cmd-check.test.js @@ -0,0 +1,103 @@ +'use strict' + +const fs = require('fs') +const v8 = require('v8') +const { test } = require('tap') +const async = require('async') +const rimraf = require('rimraf') +const ClinicDoctor = require('../index.js') + +test('cmd - test check - data exists', function (t) { + const tool = new ClinicDoctor({ dest: './foo' }) + + function cleanup (err, dirname) { + t.ifError(err) + + t.match(dirname, /^foo(\/|\\)[0-9]+\.clinic-doctor$/) + + rimraf(dirname, function (err) { + t.ifError(err) + t.end() + }) + } + + tool.collect( + [process.execPath, '-e', 'setTimeout(() => {}, 400)'], + function (err, dirname) { + if (err) return cleanup(err, dirname) + + tool.check(dirname, function (err, result) { + if (err) return cleanup(err, dirname) + + t.ok(result) + t.same(result.issue, 'data') + t.same(result.recommendation.title, 'data analysis issue') + cleanup(null, dirname) + }) + } + ) +}) + +test('cmd - test check - memory exhausted', function (t) { + const tmp = process.memoryUsage + const HEAP_MAX = v8.getHeapStatistics().heap_size_limit + + // Mock the used function to pretend the memory is exhausted. + process.memoryUsage = () => { + return { + rss: 0, + heapTotal: HEAP_MAX, + heapUsed: 0, + external: 0 + } + } + + const tool = new ClinicDoctor() + + function cleanup (err, dirname) { + process.memoryUsage = tmp + t.ifError(err) + + t.match(dirname, /^[0-9]+\.clinic-doctor$/) + + rimraf(dirname, function (err) { + t.ifError(err) + t.end() + }) + } + + tool.on('warning', function (warning) { + t.equal(warning, 'Truncating input data due to memory constrains') + }) + tool.on('truncate', function (undef) { + t.equal(undef, undefined) + }) + + tool.collect( + [process.execPath, '-e', 'setTimeout(() => {}, 400)'], + function (err, dirname) { + if (err) return cleanup(err, dirname) + + tool.check(dirname, function (err, result) { + if (err) return cleanup(err, dirname) + + t.ok(result) + t.same(result.issue, 'data') + t.same(result.recommendation.title, 'data analysis issue') + cleanup(null, dirname) + }) + } + ) +}) + +test('cmd - test check - missing data', function (t) { + const tool = new ClinicDoctor({ debug: true }) + + tool.check( + 'missing.clinic-doctor', + function (err) { + t.ok(err.message.includes('ENOENT: no such file or directory')) + t.end() + } + ) +}) From 89e4f92a67504091033ac7a0bdd9a871bf070a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Thu, 28 Nov 2019 12:29:01 +0100 Subject: [PATCH 04/10] Rename to analyze() --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index da31cddb..609cd3c6 100644 --- a/index.js +++ b/index.js @@ -167,7 +167,7 @@ class ClinicDoctor extends events.EventEmitter { } } - check (dataDirname, callback) { + analyze (dataDirname, callback) { const { analysis } = this.createAnalysis(dataDirname) analysis .on('error', callback) From 212349c9067fe2e9cfa4f038dffa1c3d4c05a15f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Thu, 28 Nov 2019 12:29:18 +0100 Subject: [PATCH 05/10] Remove `result.recommendation`, clinic cli can do this manually --- index.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/index.js b/index.js index 609cd3c6..a38521ec 100644 --- a/index.js +++ b/index.js @@ -25,10 +25,6 @@ const buildJs = require('@nearform/clinic-common/scripts/build-js') const buildCss = require('@nearform/clinic-common/scripts/build-css') const mainTemplate = require('@nearform/clinic-common/templates/main') -function getRecommendation (issueCategory) { - return recommendations.find(rec => rec.category === issueCategory) -} - class ClinicDoctor extends events.EventEmitter { constructor (settings = {}) { super() @@ -172,7 +168,6 @@ class ClinicDoctor extends events.EventEmitter { analysis .on('error', callback) .once('data', (result) => { - result.recommendation = getRecommendation(result.issueCategory) callback(null, result) }) } @@ -312,7 +307,6 @@ class ClinicDoctor extends events.EventEmitter { if (err) { callback(err) } else { - result.recommendation = getRecommendation(result.issueCategory) callback(null, result) } } From 001f54ddf36b5aab1e2bc7740cf258588eafad2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Thu, 28 Nov 2019 12:32:30 +0100 Subject: [PATCH 06/10] update docs to doctor.analyze() --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a888bf1a..aeaf030d 100644 --- a/README.md +++ b/README.md @@ -68,14 +68,13 @@ directory will be the value in the callback. stdout, stderr, and stdin will be relayed to the calling process. As will the `SIGINT` event. -#### `doctor.check(dataFilename, callback)` +#### `doctor.analyze(dataFilename, callback)` Will consume the datafile specified by `dataFilename`, this datafile will be produced by the sampler using `doctor.collect`. -`doctor.check` will then analyse the data file and call the -`callback(err, result)` with an object containing possible issues and a -recommendation on `result.recommendation`. +`doctor.analyze` will then analyse the data file and call the +`callback(err, result)` with an object containing possible issues. #### `doctor.visualize(dataFilename, outputFilename, callback)` From f571be6fcd9eb8f85d57d9d28c8ba5eef6240ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Thu, 28 Nov 2019 12:37:19 +0100 Subject: [PATCH 07/10] lint fix --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index 8e763773..87d67217 100644 --- a/index.js +++ b/index.js @@ -17,7 +17,6 @@ const SystemInfoDecoder = require('./format/system-info-decoder.js') const TraceEventDecoder = require('./format/trace-event-decoder.js') const ProcessStatDecoder = require('./format/process-stat-decoder.js') const RenderRecommendations = require('./recommendations/index.js') -const { recommendations } = RenderRecommendations const minifyStream = require('minify-stream') const v8 = require('v8') const HEAP_MAX = v8.getHeapStatistics().heap_size_limit From be24a7107788b1e6b153def80cfbe72165786fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Tue, 3 Dec 2019 12:49:27 +0100 Subject: [PATCH 08/10] update tests for analyze() --- test/{cmd-check.test.js => cmd-analyze.test.js} | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) rename test/{cmd-check.test.js => cmd-analyze.test.js} (82%) diff --git a/test/cmd-check.test.js b/test/cmd-analyze.test.js similarity index 82% rename from test/cmd-check.test.js rename to test/cmd-analyze.test.js index 71c8abf2..3ec35428 100644 --- a/test/cmd-check.test.js +++ b/test/cmd-analyze.test.js @@ -7,7 +7,7 @@ const async = require('async') const rimraf = require('rimraf') const ClinicDoctor = require('../index.js') -test('cmd - test check - data exists', function (t) { +test('cmd - test analyze - data exists', function (t) { const tool = new ClinicDoctor({ dest: './foo' }) function cleanup (err, dirname) { @@ -26,19 +26,18 @@ test('cmd - test check - data exists', function (t) { function (err, dirname) { if (err) return cleanup(err, dirname) - tool.check(dirname, function (err, result) { + tool.analyze(dirname, function (err, result) { if (err) return cleanup(err, dirname) t.ok(result) t.same(result.issue, 'data') - t.same(result.recommendation.title, 'data analysis issue') cleanup(null, dirname) }) } ) }) -test('cmd - test check - memory exhausted', function (t) { +test('cmd - test analyze - memory exhausted', function (t) { const tmp = process.memoryUsage const HEAP_MAX = v8.getHeapStatistics().heap_size_limit @@ -78,22 +77,21 @@ test('cmd - test check - memory exhausted', function (t) { function (err, dirname) { if (err) return cleanup(err, dirname) - tool.check(dirname, function (err, result) { + tool.analyze(dirname, function (err, result) { if (err) return cleanup(err, dirname) t.ok(result) t.same(result.issue, 'data') - t.same(result.recommendation.title, 'data analysis issue') cleanup(null, dirname) }) } ) }) -test('cmd - test check - missing data', function (t) { +test('cmd - test analyze - missing data', function (t) { const tool = new ClinicDoctor({ debug: true }) - tool.check( + tool.analyze( 'missing.clinic-doctor', function (err) { t.ok(err.message.includes('ENOENT: no such file or directory')) From 35c19b711ba769318b760ba1b90ba7980edfdc43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Tue, 3 Dec 2019 12:59:06 +0100 Subject: [PATCH 09/10] Return analysis object with streams from `analyze()` --- README.md | 15 +++--- debug/visualize-mod.js | 2 +- index.js | 20 ++++---- test/cmd-analyze.test.js | 98 +++++++++++++++++++++++++++++++--------- 4 files changed, 93 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index aeaf030d..6ce5cfa9 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,8 @@ const doctor = new ClinicDoctor() doctor.collect(['node', './path-to-script.js'], function (err, filepath) { if (err) throw err - doctor.visualize(filepath, filepath + '.html', function (err) { + const analysis = doctor.analyze(filepath) + doctor.visualize(analysis, filepath + '.html', function (err) { if (err) throw err }); }) @@ -68,18 +69,18 @@ directory will be the value in the callback. stdout, stderr, and stdin will be relayed to the calling process. As will the `SIGINT` event. -#### `doctor.analyze(dataFilename, callback)` +#### `doctor.analyze(dataFilename)` Will consume the datafile specified by `dataFilename`, this datafile will be produced by the sampler using `doctor.collect`. -`doctor.analyze` will then analyse the data file and call the -`callback(err, result)` with an object containing possible issues. +`doctor.analyze` will then return an object that can be passed to +`doctor.visualize()` to generate a visualization. -#### `doctor.visualize(dataFilename, outputFilename, callback)` +#### `doctor.visualize(analysis, outputFilename, callback)` -Will consume the datafile specified by `dataFilename`, this datafile will be -produced by the sampler using `doctor.collect`. +Will consume the analysis specified by `analysis`, which can be produced by +calling `doctor.analyze()`. `doctor.visualize` will then output a standalone HTML file to `outputFilename`. When completed the `callback` will be called with no extra arguments, except a diff --git a/debug/visualize-mod.js b/debug/visualize-mod.js index 9e2f7973..74b97cef 100644 --- a/debug/visualize-mod.js +++ b/debug/visualize-mod.js @@ -8,7 +8,7 @@ module.exports = { const tool = new Tool({ debug: true }) tool.visualize( - file, + tool.analyze(file), file + '.html', function (err) { if (err) { diff --git a/index.js b/index.js index 87d67217..4bbb2ece 100644 --- a/index.js +++ b/index.js @@ -135,7 +135,7 @@ class ClinicDoctor extends events.EventEmitter { }) } - createAnalysis (dataDirname) { + analyze (dataDirname) { const paths = getLoggingPaths({ path: dataDirname }) const systemInfoReader = pumpify.obj( @@ -161,16 +161,7 @@ class ClinicDoctor extends events.EventEmitter { } } - analyze (dataDirname, callback) { - const { analysis } = this.createAnalysis(dataDirname) - analysis - .on('error', callback) - .once('data', (result) => { - callback(null, result) - }) - } - - visualize (dataDirname, outputFilename, callback) { + visualize (analysisData, outputFilename, callback) { const fakeDataPath = path.join(__dirname, 'visualizer', 'data.json') const stylePath = path.join(__dirname, 'visualizer', 'style.css') const scriptPath = path.join(__dirname, 'visualizer', 'main.js') @@ -178,12 +169,17 @@ class ClinicDoctor extends events.EventEmitter { const nearFormLogoPath = path.join(__dirname, 'visualizer', 'nearform-logo.svg') const clinicFaviconPath = path.join(__dirname, 'visualizer', 'clinic-favicon.png.b64') + // back compat + if (typeof analysisData === 'string') { + analysisData = this.analyze(analysisData) + } + // Load data const { traceEventReader, processStatReader, analysis - } = this.createAnalysis(dataDirname) + } = analysisData let result = null diff --git a/test/cmd-analyze.test.js b/test/cmd-analyze.test.js index 3ec35428..267f2f0e 100644 --- a/test/cmd-analyze.test.js +++ b/test/cmd-analyze.test.js @@ -1,11 +1,19 @@ 'use strict' +/* eslint-disable */ const fs = require('fs') const v8 = require('v8') const { test } = require('tap') +const pump = require('pump') const async = require('async') const rimraf = require('rimraf') +const mkdirp = require('mkdirp') +const semver = require('semver') +const startpoint = require('startpoint') const ClinicDoctor = require('../index.js') +const getLoggingPaths = require('@nearform/clinic-common').getLoggingPaths('doctor') +const generateProcessStat = require('./generate-process-stat.js') +const generateTraceEvent = require('./generate-trace-event.js') test('cmd - test analyze - data exists', function (t) { const tool = new ClinicDoctor({ dest: './foo' }) @@ -21,20 +29,65 @@ test('cmd - test analyze - data exists', function (t) { }) } - tool.collect( - [process.execPath, '-e', 'setTimeout(() => {}, 400)'], - function (err, dirname) { - if (err) return cleanup(err, dirname) + const systemInfo = { + clock: { + hrtime: process.hrtime(), + unixtime: Date.now() + }, + nodeVersions: process.versions + } + + const badCPU = generateProcessStat({ + cpu: [ + 200, 200, 15, 10, 190, 200, 5, 15, 190, 200, + 200, 200, 15, 10, 190, 200, 5, 15, 190, 200 + ] + }, 0) + + const goodMemoryGC = generateTraceEvent( + '-S....-S....-S....-S....-S....-S....-S....-S....-S....-S....' + + '-M....-M....-S....-M....-M....-S....-M....-M....-FFF.. CCC..' + ) + + const paths = getLoggingPaths({ + identifier: 1234, + path: tool.path + }) + + mkdirp(paths['/'], ondir) + + function ondir (err) { + if (err) return cleanup(err, paths['/']) + const ProcessStatEncoder = require('../format/process-stat-encoder.js') + + async.parallel({ + systeminfo (callback) { + fs.writeFile(paths['/systeminfo'], JSON.stringify(systemInfo), callback) + }, + processstat (callback) { + pump( + startpoint(badCPU, { objectMode: true }), + new ProcessStatEncoder(), + fs.createWriteStream(paths['/processstat']), + callback) + }, + traceevent (callback) { + fs.writeFile(paths['/traceevent'], JSON.stringify({ traceEvents: goodMemoryGC }), callback) + } + }, oncollected) + } - tool.analyze(dirname, function (err, result) { - if (err) return cleanup(err, dirname) + function oncollected (err) { + if (err) return cleanup(err, paths['/']) + tool.analyze(paths['/']).analysis + .on('error', function (err) { cleanup(err, paths['/']) }) + .on('data', function (result) { t.ok(result) - t.same(result.issue, 'data') - cleanup(null, dirname) + t.same(result.issueCategory, 'performance') + cleanup(null, paths['/']) }) - } - ) + } }) test('cmd - test analyze - memory exhausted', function (t) { @@ -77,13 +130,13 @@ test('cmd - test analyze - memory exhausted', function (t) { function (err, dirname) { if (err) return cleanup(err, dirname) - tool.analyze(dirname, function (err, result) { - if (err) return cleanup(err, dirname) - - t.ok(result) - t.same(result.issue, 'data') - cleanup(null, dirname) - }) + tool.analyze(dirname).analysis + .on('error', function (err) { cleanup(err, dirname) }) + .on('data', function (result) { + t.ok(result) + t.same(result.issueCategory, 'data') + cleanup(null, dirname) + }) } ) }) @@ -91,11 +144,12 @@ test('cmd - test analyze - memory exhausted', function (t) { test('cmd - test analyze - missing data', function (t) { const tool = new ClinicDoctor({ debug: true }) - tool.analyze( - 'missing.clinic-doctor', - function (err) { + tool.analyze('missing.clinic-doctor').analysis + .on('error', function (err) { t.ok(err.message.includes('ENOENT: no such file or directory')) t.end() - } - ) + }) + .on('data', function () { + t.fail('should error') + }) }) From c4a7ae3fb339fec4725b473becade45f1a9e8812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Tue, 3 Dec 2019 13:20:48 +0100 Subject: [PATCH 10/10] generate a whole bunch of cpu data --- test/cmd-analyze.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/cmd-analyze.test.js b/test/cmd-analyze.test.js index 267f2f0e..82a5137f 100644 --- a/test/cmd-analyze.test.js +++ b/test/cmd-analyze.test.js @@ -39,6 +39,13 @@ test('cmd - test analyze - data exists', function (t) { const badCPU = generateProcessStat({ cpu: [ + // duplicate a bunch of times so the interval trimming code + // doesn't discard everything + 200, 200, 15, 10, 190, 200, 5, 15, 190, 200, + 200, 200, 15, 10, 190, 200, 5, 15, 190, 200, + 200, 200, 15, 10, 190, 200, 5, 15, 190, 200, + 200, 200, 15, 10, 190, 200, 5, 15, 190, 200, + 200, 200, 15, 10, 190, 200, 5, 15, 190, 200, 200, 200, 15, 10, 190, 200, 5, 15, 190, 200, 200, 200, 15, 10, 190, 200, 5, 15, 190, 200 ]