In CryptoJS libraries for Google Apps Script I mentioned that they were kind of slow under Apps Script - depending on the cryptography - up to a second for each one to encrypt/decrypt. Testing this kind of thing is pretty tricky when you run out of quota all the time. Here's how I tested thousands at once with no quota problems.

Here's a snap of the run - We got 40 minutes of uninterrupted processing over 13 minutes, and processed 16,000 encrypt/decrypt pairs. To speed it up even more all I need to do is to create additional processing threads in the profile.

Orchestration profile

The action is controlled by an orchestration profile. But there are some wrinkles in this one, because I wanted to make it dynamic, depending on the size of the run. Here's how it's built up. Creating the orchestration is a good first step. We don't even have to write the functions that do the work at this stage.

Create the test data

I'm creating 4000 test items. 2 threads will do for this trivial process.

// first stage - create test data

var scale = optScale || 2000, CHUNKS = 2;

var profileTestData = [];

for (var i =0; i <CHUNKS;i++ ) {

profileTestData.push ({

name: 'testdata-'+i,

functionName:'cryptoTestData',

options:{

scale:scale

}

});

}

Reduce the results

After a parallel map operation, you always need to reduce the results. This ensures you have removed any dependencies between how the previous step was executed. I use a standard function for all reduce functions.

// next reduce the test data to one

var profileReduction = [];

profileReduction.push({

name: 'reduction',

functionName:'reduceTheResults',

options:{}

});

Do the encryption

I'm applying 4 different kind of encryption to each of the randomly generated messages and passwords that were created in the first process. I'm going to give 2 threads to each encryption method, so there will be 8 threads, each of which will handle 2000 encryptions. I'll explain later what we're going to use the options for.

// next,execute the cryptotests

var CRYPTOTHREADS =2,CIPHERS=['tripledes','aes','rabbit','des'];

var profileCrypto = [];

for (var i = 0; i < CRYPTOTHREADS ; i++) {

CIPHERS.forEach (function (d) {

profileCrypto.push ({

name:'crypto-'+d+'-'+i,

functionName:'cryptoEncrypt',

options: {

index: i,

threads:CRYPTOTHREADS,

cipher:d

}

});

});

}

Reduce the results

Again we need to reduce the results. We can just use the same reduction profile as previously

Check that it worked

Decrypting everything and checking against the original messages will show that the operation worked. This time I'll go for 6 threads. I'll explain the option values later.

// next we'll test the results by decrypting and check against original

var DECRYPTOTHREADS = 6;

var profileDecrypto = [];

for (var i=0; i < DECRYPTOTHREADS ;i++ ) {

profileDecrypto.push ({

name:'decrypto-'+i,

functionName:'cryptoDecryptCheck',

options: {

stopOnFail:true,

index:i,

threads:DECRYPTOTHREADS

}

});

}

Reduce the results

Again we need to reduce the results. We can just use the same reduction profile as previously.

Log the results

We want to make sure that we ended up with the same number of items that we started with, so do a simple summary of the reduced data. Most of the options here are about Database abstraction with google apps script, which I always use to simplify writing data to spreadsheets.

// finally log the results

var profileLog = [{

"name": "LOG",

"functionName": "cryptoLog",

"skip": false,

"options": {

"driver": "cDriverSheet",

"clear": true,

"parameters": {

"siloid": "logencrypt",

"dbid": "1yTQFdN_O2nFb9obm7AHCTmPKpf5cwAd78uNQJiCcjPk",

"peanut": "bruce"

}

}

}];

Here's what was logged

Put it all together

Now we've created a profile for each section, we can put it all together like so. I'll repeat the whole function at the end of this page

// put it all together

profile.push (

profileTestData,

profileReduction,

profileCrypto,

profileReduction,

profileDecrypto,

profileReduction

);

if (cUseful.applyDefault(optLog, true) ) {

profile.push(profileLog);

}

The executor functions

These are called to do the work by the profiles we set up above. Each functionName property should point to one of these. In all cases executor functions are passed 2 parameters

The options you set up in your profile

The data from the previous stage (if there was any)

Creating the test data.

This creates a chunk of random test messages and passwords - volume as defined in options.scale.

/**

* create some test data for encryption tests

* @param {object} options describes what to do

* @param {object} reduceResults this would contain results from a previous stage if present

Do some encryptions

A number of these will be run in parallel threads. Since the data it will be passed will be all the test data, it needs to have a mechanism of deciding which part of the test data it will work on. In options we passed index and threads properties. From this it can select a section of the testdata unique to this this thread. A more sophisticated algorithm might include adjusting the number of items for this thread according to the estimated complexity, but I'm just doing a simple equal distribution between threads. It will return the encrypted message, plus everything a future decrypt function will need to validate that it worked.

/**

* do a chunk of encryption testing

* @param {object} options describes what to do

* @param {object} reduceResults this would contain results from a previous stage if present