<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: James Thomas</title>
    <description>The latest articles on DEV Community by James Thomas (@jthomas).</description>
    <link>https://dev.to/jthomas</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F91095%2F6a8b2464-5b54-4354-a8c4-bbd9282e4006.png</url>
      <title>DEV Community: James Thomas</title>
      <link>https://dev.to/jthomas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jthomas"/>
    <language>en</language>
    <item>
      <title>Faster File Transfers With Serverless</title>
      <dc:creator>James Thomas</dc:creator>
      <pubDate>Thu, 29 Aug 2019 10:42:13 +0000</pubDate>
      <link>https://dev.to/ibmdeveloper/faster-file-transfers-with-serverless-1lop</link>
      <guid>https://dev.to/ibmdeveloper/faster-file-transfers-with-serverless-1lop</guid>
      <description>&lt;p&gt;This week I've been helping a client speed up file transfers between cloud object stores using serverless.&lt;/p&gt;

&lt;p&gt;They had a 120GB file on a cloud provider's object store. This needed copying into a different cloud object store for integration with platform services. Their current file transfer process was to download the file locally and then re-upload using a development machine. This was taking close to three hours due to bandwidth issues.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Having heard about the capabilities of serverless cloud platforms, they were wondering if they could use the massive parallelism that serverless provides to speed up that process?&lt;/em&gt; 🤔&lt;/p&gt;

&lt;p&gt;After some investigating, I worked out a way to use serverless to implement concurrent file transfers. &lt;strong&gt;Transfer time was reduced from THREE HOURS to just FOUR MINUTES!&lt;/strong&gt; This was a decrease in total transfer time of 98%. 👏👏👏&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In this blog post, I'll outlined the simple steps I used to make this happen. I've been using &lt;a href="https://cloud.ibm.com/functions"&gt;IBM Cloud Functions&lt;/a&gt; as the serverless platform. Two different &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html"&gt;S3-compatible&lt;/a&gt; Object Stores were used for the file transfers. The approach should work for any object store with the features outlined below.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  S3-Compatible API Features
&lt;/h2&gt;

&lt;p&gt;Both object stores being used for the file transfers provided an &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html"&gt;S3-compatible API&lt;/a&gt;. The S3 API has two features that, when combined, enable concurrent file transfers: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests"&gt;Range Reads&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/mpuoverview.html"&gt;Multi-Part Transfers&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Range Reads
&lt;/h3&gt;

&lt;p&gt;The HTTP/1.1 protocol defines a &lt;code&gt;Range&lt;/code&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests"&gt;header&lt;/a&gt; which allows the client to retrieve part of a document. The client specifies a byte range using the header value, e.g. &lt;code&gt;Range: bytes=0-499&lt;/code&gt;. The byte values are then returned in the HTTP response with a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206"&gt;HTTP 206&lt;/a&gt; status code. If the byte range is invalid, a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416"&gt;HTTP 416&lt;/a&gt; response is returned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The S3 API supports &lt;code&gt;Range&lt;/code&gt; request headers on &lt;code&gt;GET&lt;/code&gt; HTTP requests for object store files.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sending a HTTP HEAD request for an object store file will return the file size (using the &lt;code&gt;Content-Length&lt;/code&gt; header value). Creating ranges for fixed byte chunks up to this file size  (&lt;code&gt;0-1023&lt;/code&gt;, &lt;code&gt;1024-2047&lt;/code&gt;,&lt;code&gt;2048-3072&lt;/code&gt; ...) allows all sections of a file to be retrieve in parallel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Part Transfers
&lt;/h3&gt;

&lt;p&gt;Files are uploaded to buckets using HTTP PUT requests. These operations supports a maximum file size of 5GB. Uploading larger files is only possible using "Multi-Part" transfers.&lt;/p&gt;

&lt;p&gt;Clients &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadInitiate.html"&gt;initiate a multi-part transfer&lt;/a&gt; using the API and are returned an upload identifier. The large file is then split into parts which are uploaded using &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadUploadPart.html"&gt;individual HTTP PUT requests&lt;/a&gt;. The upload identifier is used to tags individual requests as belonging to the same file. Once all parts have been uploaded, the API is used to &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadComplete.html"&gt;confirm the file is finished&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;File parts do not have to be uploaded in consecutive order and multiple parts can be uploaded simultaneously.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless File Transfers
&lt;/h2&gt;

&lt;p&gt;Combing these two features, I was able to create a serverless function to copy a part of a file between source and destination buckets. By invoking thousands of these functions in parallel, the entire file could be simultaneously copied in parallel streams between buckets. This was controlled by a local script used to manage the function invocations, monitor progress and complete the multi-part transfer once invocations had finished.&lt;/p&gt;

&lt;h3&gt;
  
  
  Serverless Function
&lt;/h3&gt;

&lt;p&gt;The serverless function copies a file part between object stores. It is invoked with all the parameters needed to access both bucket files, byte range to copy and multi-part transfer identifier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;src_bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;src_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dest_bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dest_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mpu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;byte_range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;read_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src_bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;src_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upload_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;upload_part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dest_bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dest_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mpu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;byte_range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;upload_result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Read Source File Part
&lt;/h4&gt;

&lt;p&gt;The S3-API JS client can create a "&lt;em&gt;Range Read&lt;/em&gt;" request by passing the &lt;code&gt;Range&lt;/code&gt; parameter with the byte range value, e.g. &lt;code&gt;bytes=0-NN&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;read_range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file_range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;file_range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Upload File Part
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;uploadPart&lt;/code&gt; method is used to complete a part of a multi-part transfer. The method needs the &lt;code&gt;UploadID&lt;/code&gt; created when initiating the multi-part transfer and the &lt;code&gt;PartNumber&lt;/code&gt; for the chunk index. ETags for the uploaded content will be returned.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upload_part&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UploadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PartNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploadPart&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UploadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PartNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: The &lt;code&gt;uploadPart&lt;/code&gt; method does not support streaming &lt;code&gt;Body&lt;/code&gt; values unless they come from the filesystem. This means the entire part has to be read into memory before uploading. The serverless function must have enough memory to handle this.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Local Script
&lt;/h3&gt;

&lt;p&gt;The local script used to invoke the functions has to do the following things...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create and complete the multi-part transfer&lt;/li&gt;
&lt;li&gt;Calculate file part byte ranges for function input parameters&lt;/li&gt;
&lt;li&gt;Copy file parts using concurrent functions invocations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Create Multi-Part Transfers
&lt;/h4&gt;

&lt;p&gt;The S3-API JS client can be used to create a new Multi-Part Transfer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UploadId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createMultipartUpload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;UploadId&lt;/code&gt; can then be used as an input parameter to the serverless function.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create Byte Ranges
&lt;/h4&gt;

&lt;p&gt;Source file sizes can be retrieved using the client library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ContentLength&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ContentLength&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This file size needs splitting into consecutive byte ranges of fixed size chunks. This function will return an array of the HTTP Range header values (&lt;code&gt;bytes=N-M&lt;/code&gt;) needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;split_into_ranges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;range_mbs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;range_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;range_mbs&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ranges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;range_offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;last_byte_range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;range_offset&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;last_byte_range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;range_offset&lt;/span&gt;
    &lt;span class="c1"&gt;// Last byte range may be less than chunk size where file size&lt;/span&gt;
    &lt;span class="c1"&gt;// is not an exact multiple of the chunk size.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;range_size&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;last_byte_range&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;ranges&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`bytes=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;range_offset&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;range_size&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ranges&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Invoke Concurrent Functions
&lt;/h4&gt;

&lt;p&gt;Serverless functions need to be invoked for each byte range calculated above. Depending on the file and chunk sizes used, the number of invocations needed could be larger than the platform's concurrency rate limit (defaults to 1000 on &lt;a href="https://cloud.ibm.com/functions/"&gt;IBM Cloud Functions&lt;/a&gt;). In the example above (120GB file in 100MB chunks), 1229 invocations would be needed.&lt;/p&gt;

&lt;p&gt;Rather than executing all the byte ranges at once, the script needs to use a maximum of 1000 concurrent invocations. When initial invocations finish, additional functions can be invoked until all the byte ranges have been processed. This code snippet shows a solution to this issue (using &lt;a href="https://github.com/apache/openwhisk-client-js"&gt;IBM Cloud Functions JS SDK&lt;/a&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parallel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;async-await-parallel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;retry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;async-retry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openwhisk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openwhisk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;concurrent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;static_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source_bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dest_bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;source_filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dest_filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mpu&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;openwhisk&lt;/span&gt;&lt;span class="p"&gt;({...});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket_file_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source_bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;source_filename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ranges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;split_into_ranges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bucket_file_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chunk_size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uploads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ranges&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoke&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;static_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upload_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;blocking&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;upload_result&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uploads&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;uploads&lt;/code&gt; value is an array of lazily evaluated serverless function invocations. The code snippet uses the &lt;code&gt;async-await-parallel&lt;/code&gt; &lt;a href="https://www.npmjs.com/package/async-await-parallel"&gt;library&lt;/a&gt; to limit the number of concurrent invocations. Handling intermittent or erroneous invocation errors is managed using the &lt;code&gt;async-retry&lt;/code&gt; &lt;a href="https://www.npmjs.com/package/async-retry"&gt;library&lt;/a&gt;. Failed invocations will be retried three times.&lt;/p&gt;

&lt;h4&gt;
  
  
  Finish Multi-Part Transfer
&lt;/h4&gt;

&lt;p&gt;Once all parts have been uploaded, ETags (returned from the serverless invocations) and the Part Numbers are used to complete the multi-part transfer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PartNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ETag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completeMultipartUpload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;UploadId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;MultipartUpload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Parts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;The previous file transfer process (download locally and re-upload from development machine) was taking close to &lt;strong&gt;three hours&lt;/strong&gt;. This was an average throughput rate of 1.33MB/s ((120GB * 2) / 180).&lt;/p&gt;

&lt;p&gt;Using serverless functions, the entire process was completed in &lt;strong&gt;FOUR MINUTES&lt;/strong&gt;. File chunks of 100MB were transferred in parallel using 1229 function invocations. This was an average throughput rate of 60MB/s. &lt;strong&gt;That was a reduction in total transfer time of ~98%.&lt;/strong&gt; 💯💯💯&lt;/p&gt;

&lt;p&gt;Serverless makes it incredibly easy to run &lt;a href="https://en.wikipedia.org/wiki/Embarrassingly_parallel"&gt;embarrassingly parallel&lt;/a&gt; workloads in the cloud. With just a few lines of code, the file transfer process can be parallelised using 1000s of concurrent functions. The client was rather impressed as you can imagine... 😎&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>cloud</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>Serverless Functions With WebAssembly Modules</title>
      <dc:creator>James Thomas</dc:creator>
      <pubDate>Thu, 08 Aug 2019 09:50:20 +0000</pubDate>
      <link>https://dev.to/ibmdeveloper/serverless-functions-with-webassembly-modules-343e</link>
      <guid>https://dev.to/ibmdeveloper/serverless-functions-with-webassembly-modules-343e</guid>
      <description>&lt;p&gt;Watching a &lt;a href="https://london.serverlessdays.io/speakers/lin/" rel="noopener noreferrer"&gt;recent talk&lt;/a&gt; by &lt;a href="https://twitter.com/linclark" rel="noopener noreferrer"&gt;Lin Clark&lt;/a&gt; and &lt;a href="https://twitter.com/tschneidereit" rel="noopener noreferrer"&gt;Till Schneidereit&lt;/a&gt; about &lt;a href="https://webassembly.org/" rel="noopener noreferrer"&gt;WebAssembly&lt;/a&gt; (Wasm) inspired me to start experimenting with using WebAssembly &lt;a href="https://webassembly.org/docs/modules/" rel="noopener noreferrer"&gt;modules&lt;/a&gt; from &lt;a href="https://en.wikipedia.org/wiki/Serverless_computing" rel="noopener noreferrer"&gt;serverless functions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This blog post demonstrates how to invoke functions written in C from Node.js serverless functions. Source code in C is compiled to Wasm modules and bundled in the deployment package. Node.js code implements the serverless platform handler and calls native functions upon invocations.&lt;/p&gt;

&lt;p&gt;The examples should work (with some modifications) on any serverless platform that supports deploying Node.js functions from a zip file. I'll be using &lt;a href="https://cloud.ibm.com/functions/" rel="noopener noreferrer"&gt;IBM Cloud Functions&lt;/a&gt; (&lt;a href="https://openwhisk.apache.org/" rel="noopener noreferrer"&gt;Apache OpenWhisk&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  WebAssembly
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;WebAssembly (abbreviated &lt;em&gt;Wasm&lt;/em&gt;) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://webassembly.org/" rel="noopener noreferrer"&gt;https://webassembly.org/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Wasm started as a project to run low-level languages in the browser. This was envisioned as a way to execute computationally intensive tasks in the client, e.g. image manipulation, machine learning, graphics engines. This would improve performance for those tasks compared to using JavaScript.&lt;/p&gt;

&lt;p&gt;WebAssembly compiles languages like C, C++ and Rust to a portable instruction format, rather than platform-specific machine code. Compiled Wasm files are interpreted by a Wasm VM in the browser or other runtimes. &lt;a href="https://developer.mozilla.org/en-US/docs/WebAssembly/Using_the_JavaScript_API" rel="noopener noreferrer"&gt;APIs have been defined&lt;/a&gt; to support importing and executing Wasm modules from JavaScript runtimes. These APIs have been implemented in multiple browsers and recent Node.js versions (v8.0.0+).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This means Node.js serverless functions, using a runtime version above 8.0.0, can use WebAssembly!&lt;/strong&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Wasm Modules + Serverless
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;"Why would we want to use WebAssembly Modules from Node.js Serverless Functions?"&lt;/em&gt; 🤔&lt;/p&gt;

&lt;h4&gt;
  
  
  Performance
&lt;/h4&gt;

&lt;p&gt;Time is literally money with serverless platforms. The faster the code executes, the less it will cost. Using C, C++ or Rust code, compiled to Wasm modules, for &lt;a href="https://medium.com/@torch2424/webassembly-is-fast-a-real-world-benchmark-of-webassembly-vs-es6-d85a23f8e193" rel="noopener noreferrer"&gt;computationally intensive tasks&lt;/a&gt; can be much faster than the same algorithms implemented in JavaScript.&lt;/p&gt;

&lt;h4&gt;
  
  
  Easier use of native libraries
&lt;/h4&gt;

&lt;p&gt;Node.js already &lt;a href="https://github.com/nodejs/node-gyp" rel="noopener noreferrer"&gt;has a way&lt;/a&gt; to use native libraries (in C or C++) from the runtime. This works by compiling the native code during the NPM installation process. Libraries bundled in deployment packages need to be compiled for the serverless platform runtime, not the development environment.&lt;/p&gt;

&lt;p&gt;Developers often resort to using &lt;a href="https://github.com/apache/openwhisk/blob/master/docs/actions-nodejs.md#handling-npm-libraries-with-native-dependencies" rel="noopener noreferrer"&gt;specialised containers&lt;/a&gt; or &lt;a href="https://aws.amazon.com/blogs/compute/nodejs-packages-in-lambda/" rel="noopener noreferrer"&gt;VMs&lt;/a&gt;, that try to match the runtime environments, for library compilation. This process is error-prone, difficult to debug and a source of problems for developers new to serverless.&lt;/p&gt;

&lt;p&gt;Wasm is deliberately platform independent. This means Wasm code compiled locally will work on any Wasm runtime. No more worrying about platform architectures and complex toolchains for native libraries!&lt;/p&gt;

&lt;h4&gt;
  
  
  Additional runtime support
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/appcypher/awesome-wasm-langs" rel="noopener noreferrer"&gt;Dozens of languages&lt;/a&gt; now support compiling to WebAssembly.&lt;/p&gt;

&lt;p&gt;Want to write serverless functions in Rust, C, or Lua? No problem! By wrapping Wasm modules with a small Node.js handler function, developers can write their serverless applications in any language with "compile to Wasm" support.&lt;/p&gt;

&lt;p&gt;Developers don't have to be restricted to the runtimes provided by the platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  JS APIs in Node.js
&lt;/h3&gt;

&lt;p&gt;Here is the code needed to load a Wasm module from Node.js. Wasm modules are distributed in &lt;code&gt;.wasm&lt;/code&gt; files. Loaded modules are instantiated into instances, by providing a configurable runtime environment. Functions exported from Wasm modules can then be invoked on these instances from Node.js.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wasm_module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;library.wasm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wasm_module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wasmModule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wasmMemory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wasmInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wasmModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wasmMemory&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Calling Functions
&lt;/h4&gt;

&lt;p&gt;Exported Wasm functions are available on the &lt;code&gt;exports&lt;/code&gt; property of the &lt;code&gt;wasmInstance&lt;/code&gt;. These properties can be invoked as normal functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;wasmInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Passing &amp;amp; Returning Values
&lt;/h4&gt;

&lt;p&gt;Exported Wasm functions can only receive and return &lt;a href="https://webassembly.github.io/spec/core/syntax/types.html" rel="noopener noreferrer"&gt;native Wasm types&lt;/a&gt;. This (&lt;a href="https://github.com/WebAssembly/reference-types" rel="noopener noreferrer"&gt;currently&lt;/a&gt;) means only integers. &lt;/p&gt;

&lt;p&gt;Values that can be represented as a series of numbers, e.g. strings or arrays, can be &lt;a href="https://stackoverflow.com/questions/41875728/pass-a-javascript-array-as-argument-to-a-webassembly-function" rel="noopener noreferrer"&gt;written directly&lt;/a&gt; to the Wasm instance memory heap from Node.js. Heap memory references can be passed as the function parameter values, allowing the Wasm code to read these values. More complex types (e.g. JS objects) are not supported.&lt;/p&gt;

&lt;p&gt;This process can also be &lt;a href="https://stackoverflow.com/questions/41353389/how-can-i-return-a-javascript-string-from-a-webassembly-function" rel="noopener noreferrer"&gt;used in reverse&lt;/a&gt;, with Wasm functions returning heap references to pass back strings or arrays with the function result. &lt;/p&gt;

&lt;p&gt;For more details on how memory works in Web Assembly, please see this &lt;a href="https://hacks.mozilla.org/2017/07/memory-in-webassembly-and-why-its-safer-than-you-think/" rel="noopener noreferrer"&gt;page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;p&gt;Having covered the basics, let's look at some examples...&lt;/p&gt;

&lt;p&gt;I'll start with calling a &lt;a href="https://gist.github.com/jthomas/5de757fd36b3c6904e5c5f12c8264b41" rel="noopener noreferrer"&gt;simple C function from a Node.js serverless function&lt;/a&gt;. This will demonstrate the complete steps needed to compile and use a small C program as a Wasm module. Then I'll look at a more real-world use-case, &lt;a href="https://github.com/jthomas/openwhisk-image-resize-wasm" rel="noopener noreferrer"&gt;dynamic image resizing&lt;/a&gt;. This will use a C library compiled to Wasm to improve performance.&lt;/p&gt;

&lt;p&gt;Examples will be deployed to &lt;a href="https://cloud.ibm.com/functions" rel="noopener noreferrer"&gt;IBM Cloud Functions&lt;/a&gt; (&lt;a href="https://openwhisk.apache.org/" rel="noopener noreferrer"&gt;Apache OpenWhisk&lt;/a&gt;). They should work on other serverless platforms (supporting the Node.js runtime) with small modifications to the handler function's interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simple Function Calls
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Create Source Files
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Create a file &lt;code&gt;add.c&lt;/code&gt; with the following contents:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create a file (&lt;code&gt;index.js&lt;/code&gt;) with the following contents:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;util&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;util&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WASM_MODULE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add.wasm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;wasm_instance&lt;/span&gt; 

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;load_wasm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wasm_module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;wasm_instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wasm_module&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;__memory_base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instantiate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;wasm_instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;wasm_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_add&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;load_wasm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;WASM_MODULE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create a file (&lt;code&gt;package.json&lt;/code&gt;) with the following contents:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wasm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.js"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Compile Wasm Module
&lt;/h4&gt;

&lt;p&gt;This C source file needs compiling to a WebAssembly module. There are different projects to handle this. I will be using &lt;a href="https://emscripten.org/" rel="noopener noreferrer"&gt;Emscripten&lt;/a&gt;, which uses LLVM to compile C and C++ to WebAssembly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://emscripten.org/docs/getting_started/downloads.html" rel="noopener noreferrer"&gt;Install&lt;/a&gt; the &lt;a href="https://emscripten.org/" rel="noopener noreferrer"&gt;Emscripten&lt;/a&gt; toolchain. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the following command to generate the Wasm module.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;emcc &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;WASM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;SIDE_MODULE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;EXPORTED_FUNCTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"['_add']"&lt;/span&gt; &lt;span class="nt"&gt;-O1&lt;/span&gt; add.c &lt;span class="nt"&gt;-o&lt;/span&gt; add.wasm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The &lt;code&gt;SIDE_MODULE&lt;/code&gt; option tells the compiler the Wasm module will be loaded manually using the JS APIs. This stops Emscripten generating a corresponding JS file to do this automatically. Functions exposed on the Wasm module are controlled by the &lt;code&gt;EXPORTED_FUNCTIONS&lt;/code&gt; configuration parameter.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Deploy Serverless Function
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Create deployment package with source files.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;zip action.zip index.js add.wasm package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create serverless function from deployment package.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ibmcloud wsk action create wasm action.zip --kind nodejs:10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Invoke serverless function to test Wasm module.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ibmcloud wsk action invoke wasm -r -p a 2 -p b 2
{
    "sum": 4
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works! 🎉🎉🎉&lt;/p&gt;

&lt;p&gt;Whilst this is a trivial example, it demonstrates the workflow needed to compile C source files to Wasm modules and invoke exported functions from Node.js serverless functions. Let's move onto a more realistic example...&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Image Resizing
&lt;/h3&gt;

&lt;p&gt;This &lt;a href="https://github.com/jthomas/openwhisk-image-resize-wasm" rel="noopener noreferrer"&gt;repository&lt;/a&gt; contains a serverless function to resize images using a C library called via WebAssembly. It is a fork of the &lt;a href="https://github.com/cloudflare/cloudflare-workers-wasm-demo" rel="noopener noreferrer"&gt;original code&lt;/a&gt; created by Cloudflare for their Workers platform. See the original repository for details on what the repository contains and how the files work.&lt;/p&gt;

&lt;h4&gt;
  
  
  Checkout Repository
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Retrieve the source files by checking out this &lt;a href="https://github.com/jthomas/openwhisk-image-resize-wasm" rel="noopener noreferrer"&gt;repository&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/jthomas/openwhisk-image-resize-wasm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This repository contains the pre-compiled Wasm module (&lt;code&gt;resize.wasm&lt;/code&gt;) needed to resize images using the &lt;a href="https://github.com/nothings/stb" rel="noopener noreferrer"&gt;stb library&lt;/a&gt;. The module exposes two functions: &lt;code&gt;init&lt;/code&gt; and &lt;code&gt;resize&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;init&lt;/code&gt; function &lt;a href="https://github.com/jthomas/openwhisk-image-resize-wasm/blob/master/main.c#L29-L38" rel="noopener noreferrer"&gt;returns a heap reference&lt;/a&gt; to write the image bytes for processing &lt;a href="https://github.com/jthomas/openwhisk-image-resize-wasm/blob/master/worker.js#L59" rel="noopener noreferrer"&gt;into&lt;/a&gt;. The &lt;code&gt;resize&lt;/code&gt; &lt;a href="https://github.com/jthomas/openwhisk-image-resize-wasm/blob/master/main.c#L49" rel="noopener noreferrer"&gt;function&lt;/a&gt; is called with two values, the image byte array length and new width value. It uses these values to read the image bytes from the heap and calls the library functions to resize the image to the desired width. Resized image bytes are written back to the heap and the new byte array length is returned.&lt;/p&gt;

&lt;h4&gt;
  
  
  Deploy Serverless Function
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Create deployment package from source files.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;zip action.zip resizer.wasm package.json worker.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create serverless function from deployment package.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ibmcloud wsk action update resizer action.zip --kind nodejs:10 --web true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Retrieve HTTP URL for Web Action.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ibmcloud wsk action get resizer --url
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;This should return a URL like:&lt;/em&gt; &lt;code&gt;https://&amp;lt;region&amp;gt;.cloud.ibm.com/api/v1/web/&amp;lt;ns&amp;gt;/default/resizer&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the Web Action URL with the &lt;code&gt;.http&lt;/code&gt; extension.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;region&amp;gt;.cloud.ibm.com/api/v1/web/&amp;lt;ns&amp;gt;/default/resizer.http
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should return the following image resized to 250 pixels (from 900 pixels).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbit.ly%2F2ZlP838" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbit.ly%2F2ZlP838" alt="Pug with Ice-cream"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;URL query parameters (&lt;code&gt;url&lt;/code&gt; and &lt;code&gt;width&lt;/code&gt;) can be used to modify the image source or output width for the next image, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;region&amp;gt;.cloud.ibm.com/api/v1/web/&amp;lt;ns&amp;gt;/default/resizer.http?url=&amp;lt;IMG_URL&amp;gt;&amp;amp;width=500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;WebAssembly may have started as a way to run native code in the browser, but soon expanded to server-side runtime environments like Node.js. WebAssembly modules are supported on any serverless platform with a Node.js v8.0.0+ runtime. &lt;/p&gt;

&lt;p&gt;Wasm provides a fast, safe and secure way to ship portable modules from compiled languages. Developers don't have to worry about whether the module is compiled for the correct platform architecture or linked against unavailable dynamic libraries. This is especially useful for serverless functions in Node.js, where compiling native libraries for production runtimes can be challenging.&lt;/p&gt;

&lt;p&gt;Wasm modules can be used to improve performance for computationally intensive calculations, which lowers invocation times and, therefore, costs less. It also provides an easy way to utilise additional runtimes on serverless platforms without any changes by the platform provider.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>webassembly</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>Hosting Static Websites on IBM Cloud</title>
      <dc:creator>James Thomas</dc:creator>
      <pubDate>Tue, 30 Jul 2019 14:18:02 +0000</pubDate>
      <link>https://dev.to/ibmdeveloper/hosting-static-websites-on-ibm-cloud-2ib7</link>
      <guid>https://dev.to/ibmdeveloper/hosting-static-websites-on-ibm-cloud-2ib7</guid>
      <description>&lt;p&gt;This blog post explains how to host a &lt;a href="https://en.wikipedia.org/wiki/Static_web_page"&gt;static website&lt;/a&gt; on &lt;a href="https://cloud.ibm.com"&gt;IBM Cloud&lt;/a&gt;. These websites are rendered client-side by the browser from static assets, like HTML, CSS and JS files. They do not need a server-side component to create pages dynamically at runtime. Static websites are often combined with backend APIs to create &lt;a href="https://en.wikipedia.org/wiki/Single-page_application"&gt;Single Page Applications&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hosting static websites on IBM Cloud uses &lt;a href="https://www.ibm.com/cloud/object-storage"&gt;Cloud Object Storage&lt;/a&gt; (COS) and &lt;a href="https://www.ibm.com/cloud/cloud-internet-services"&gt;Cloud Internet Services&lt;/a&gt; (CIS) (with &lt;a href="https://cloud.ibm.com/docs/infrastructure/cis?topic=cis-use-page-rules"&gt;Page Rules&lt;/a&gt; and &lt;a href="https://cloud.ibm.com/docs/infrastructure/cis?topic=cis-edge-functions"&gt;Edge Function&lt;/a&gt;s). These services provide the following features needed to serve static websites.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Auto-serving static assets from provider-managed HTTP service (Cloud Object Storage).&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom domain support to serve content from user-controlled domain name (CIS - Page Rules).&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configurable Index and Error documents (CIS - Edge Functions).&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are the steps needed to host a static website on IBM Cloud by combining those services.&lt;/p&gt;

&lt;h1&gt;
  
  
  Serving static assets
&lt;/h1&gt;

&lt;p&gt;IBM Cloud Object Storage is a scalable storage solution for cloud applications. Files are managed through a &lt;a href="https://cloud.ibm.com/docs/services/cloud-object-storage?topic=cloud-object-storage-compatibility-api"&gt;RESTful HTTP API&lt;/a&gt; and stored in user-defined collections called "buckets". Bucket files are returned as HTTP responses from &lt;a href="https://cloud.ibm.com/docs/services/cloud-object-storage?topic=cloud-object-storage-compatibility-api#compatibility-api-object"&gt;HTTP GET requests&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;COS supports an optional "&lt;em&gt;anonymous read-only access&lt;/em&gt;" &lt;a href="https://cloud.ibm.com/docs/services/cloud-object-storage?topic=cloud-object-storage-iam-public-access"&gt;setting for buckets&lt;/a&gt;. This means all files in the bucket will be accessible using anonymous HTTP GET requests.&lt;/p&gt;

&lt;p&gt;Putting HTML, CSS and JS files in a public bucket allows static websites to be served directly by COS. Users are charged for bandwidth used and HTTP requests received for all bucket files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create IBM Cloud Object Storage instance
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;If you already have an instance of Cloud Object Storage you can skip this step...&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provision &lt;a href="https://cloud.ibm.com/catalog/services/cloud-object-storage"&gt;a new instance&lt;/a&gt; of IBM Cloud Object Storage&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create IBM Cloud Object Storage Bucket
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Open the COS instance from the &lt;a href="https://cloud.ibm.com/resources"&gt;Resource List&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.ibm.com/docs/services/cloud-object-storage?topic=cloud-object-storage-getting-started#gs-create-buckets"&gt;Create a new COS bucket&lt;/a&gt; to host the static site files.

&lt;ul&gt;
&lt;li&gt;Choose a Bucket name &lt;/li&gt;
&lt;li&gt;Choose the &lt;code&gt;Resiliency,&lt;/code&gt; &lt;code&gt;Location&lt;/code&gt; and &lt;code&gt;Storage Class&lt;/code&gt; options for the bucket.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Any choices for these options can be used - it does not affect the static site hosting capability. For more details on what they mean, please see this &lt;a href="https://cloud.ibm.com/docs/services/cloud-object-storage?topic=cloud-object-storage-classes"&gt;documentation&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Upload Static Assets To Bucket
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://cloud.ibm.com/docs/services/cloud-object-storage?topic=cloud-object-storage-upload"&gt;Upload static file assets&lt;/a&gt; to the new bucket.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Enable Public Access to bucket files
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Click the &lt;em&gt;"Access Policies"&lt;/em&gt; menu item from the bucket level menu.&lt;/li&gt;
&lt;li&gt;Click the "&lt;em&gt;Public Access&lt;/em&gt;" tab from the bucket access policy page.&lt;/li&gt;
&lt;li&gt;Check the Access Group drop-down has "&lt;em&gt;Public Access&lt;/em&gt;" option selected. &lt;/li&gt;
&lt;li&gt;Click the "&lt;em&gt;Create access policy&lt;/em&gt;" and then "&lt;em&gt;Enable&lt;/em&gt;" on the pop menu.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2soX4Saf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/jthomas/jthomas.github.io/master/images/static-site-hosting/bucket-access-policy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2soX4Saf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/jthomas/jthomas.github.io/master/images/static-site-hosting/bucket-access-policy.png" alt="Bucket access policy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Check bucket files are accessible
&lt;/h3&gt;

&lt;p&gt;Bucket files should now be accessible using the service endpoint URL, bucket id and file names. COS supports providing the bucket name in the URL path or a sub-domain on the service endpoint.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the "&lt;em&gt;Configuration&lt;/em&gt;" panel on the bucket page.&lt;/li&gt;
&lt;li&gt;Retrieve the &lt;strong&gt;public endpoint&lt;/strong&gt; shown, e.g. &lt;code&gt;s3.&amp;lt;REGION&amp;gt;.cloud-object-storage.appdomain.cloud&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LNlC4sIJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/jthomas/jthomas.github.io/master/images/static-site-hosting/public-endpoint-hostname.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LNlC4sIJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/jthomas/jthomas.github.io/master/images/static-site-hosting/public-endpoint-hostname.png" alt="Public endpoint hostname"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bucket files (like &lt;code&gt;index.html&lt;/code&gt;) should now be accessible by a web browser.&lt;/strong&gt; COS supports both HTTP and HTTPS traffic. Bucket files are available using the following URLs.&lt;/p&gt;

&lt;h4&gt;
  
  
  vhost addressing
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;BUCKET_NANME&amp;gt;.s3.eu-gb.cloud-object-storage.appdomain.cloud/index.html&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  url path addressing
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;s3.&amp;lt;REGION&amp;gt;.cloud-object-storage.appdomain.cloud/&amp;lt;BUCKET_NANME&amp;gt;/index.html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Bucket files can now be referenced directly in external web applications. COS buckets are often used to store large application assets like videos or images. &lt;strong&gt;For hosting an entire website, it is often necessary to serve content from a custom domain name, rather than the COS bucket hostname.&lt;/strong&gt; &lt;/p&gt;

&lt;h1&gt;
  
  
  Custom domain support
&lt;/h1&gt;

&lt;p&gt;Cloud Internet Services Page Rules can &lt;a href="https://cloud.ibm.com/docs/infrastructure/cis?topic=cis-resolve-override-cos"&gt;automatically configure custom domain&lt;/a&gt; support for COS buckets. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/CNAME_recor"&gt;CNAME&lt;/a&gt; DNS records are created to alias the custom domain to the COS bucket hostname. All traffic to the custom domain will then be forwarded to the COS service.&lt;/p&gt;

&lt;p&gt;When COS serves files from bucket sub-domains, the HTTP  &lt;code&gt;Host&lt;/code&gt; &lt;a href="https://stackoverflow.com/questions/43156023/what-is-http-host-header"&gt;request header value&lt;/a&gt; to determine the bucket name. With CNAME DNS records, this header value will still refer to the custom domain, rather than the bucket sub-domain. This field needs to be dynamically updated with the correct value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create IBM Cloud Internet Services instance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Provision a new instance of &lt;a href="https://cloud.ibm.com/catalog/services/internet-services"&gt;Cloud Internet Services&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Register Custom Domain name with Cloud Internet Services
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Follow the &lt;a href="https://cloud.ibm.com/docs/infrastructure/cis?topic=cis-getting-started#add-configure-your-domain"&gt;documentation&lt;/a&gt; on how to register a custom domain with Cloud Internet Services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;This process involves delegating name server control for the domain over to IBM Cloud Internet Services.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Page Rules and DNS records (automatic)
&lt;/h3&gt;

&lt;p&gt;Cloud Internet Services &lt;a href="https://cloud.ibm.com/docs/infrastructure/cis?topic=cis-resolve-override-cos"&gt;can automatically set up&lt;/a&gt; Page Rules and DNS records needed to forward custom domain traffic to COS buckets. This automatically exposes the bucket as &lt;code&gt;bucket-name.your-domain.com&lt;/code&gt;. If you want to change this default sub-domain name, follow the manual steps in the next section.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the Performance drop-down menu and click the "&lt;em&gt;Page Rules&lt;/em&gt;" link.&lt;/li&gt;
&lt;li&gt;Click the "&lt;em&gt;Create rule&lt;/em&gt;" button from the table.&lt;/li&gt;
&lt;li&gt;Select the Rule Behaviour Setting as "&lt;em&gt;Resolve Override with COS&lt;/em&gt;"&lt;/li&gt;
&lt;li&gt;Select the correct COS instance and bucket.&lt;/li&gt;
&lt;li&gt;Click the "&lt;em&gt;Create&lt;/em&gt;" button.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YQMI04mt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/jthomas/jthomas.github.io/master/images/static-site-hosting/auto-page-rule.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YQMI04mt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/jthomas/jthomas.github.io/master/images/static-site-hosting/auto-page-rule.png" alt="Auto Page Rules"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Once DNS records have propagated, bucket files should be accessible using the custom domain&lt;/strong&gt;:  &lt;code&gt;http(s)://&amp;lt;CUSTOM_DOMAIN&amp;gt;/index.html&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Page Rules and DNS records (manual)
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;These steps only need following if you haven't done the section above….&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Create the Page Rule to modify the HTTP host header.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the Performance drop-down menu and select the "&lt;em&gt;Page Rules&lt;/em&gt;" link.&lt;/li&gt;
&lt;li&gt;Click the "&lt;em&gt;Create rule&lt;/em&gt;" button from the table.&lt;/li&gt;
&lt;li&gt;Set the URL match field to be &lt;code&gt;&amp;lt;SUB_DOMAIN&amp;gt;.&amp;lt;CUSTOM_DOMAIN&amp;gt;/*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select the Rule Behaviour Setting as "&lt;em&gt;Host Header Override&lt;/em&gt;" as the custom bucket sub-domain:&lt;code&gt;&amp;lt;BUCKET_NANME&amp;gt;.&amp;lt;REGION&amp;gt;.eu-gb.cloud-object-storage.appdomain.cloud&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create the DNS CNAME record to forward traffic to COS.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the Reliability drop-down menu and click the "&lt;em&gt;DNS&lt;/em&gt;" menu entry.&lt;/li&gt;
&lt;li&gt;Add a new DNS record with the following values.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type:&lt;/strong&gt; &lt;em&gt;CNAME&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; &lt;em&gt;&amp;lt;custom subdomain host&amp;gt;&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTL:&lt;/strong&gt; &lt;em&gt;Automatic&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alias Domain Name:&lt;/strong&gt; &lt;em&gt;&amp;lt;COS bucket sub-domain&amp;gt;&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Name&lt;/em&gt; is the sub-domain on the custom domain (e.g. &lt;code&gt;www&lt;/code&gt;) through which the COS bucket will be accessible. &lt;em&gt;Alias Domain Name&lt;/em&gt; is the COS bucket sub-domain from above, e.g. &lt;code&gt;&amp;lt;BUCKET_NANME&amp;gt;.&amp;lt;REGION&amp;gt;.eu-gb.cloud-object-storage.appdomain.cloud&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once the record is added, set the &lt;code&gt;Proxy&lt;/code&gt; field to true. This is necessary for the page rules to work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Once DNS records have propagated, bucket files should be accessible using the custom domain.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Configurable Index and Error pages
&lt;/h1&gt;

&lt;p&gt;COS will now serve static assets from a custom sub-domain, where file names are explicitly included in the URL, e.g. &lt;code&gt;http(s)://&amp;lt;CUSTOM_DOMAIN&amp;gt;/index.html&lt;/code&gt;. This works fine for static websites with two exceptions, the default document for the web site and the error page.&lt;/p&gt;

&lt;p&gt;When a user visits the COS bucket sub-domain without an explicit file path (&lt;code&gt;http(s)://&amp;lt;CUSTOM_DOMAIN&amp;gt;&lt;/code&gt;), the COS service will return the &lt;a href="https://cloud.ibm.com/docs/services/cloud-object-storage?topic=cloud-object-storage-compatibility-api-bucket-operations#compatibility-api-list-objects-v2"&gt;bucket file list&lt;/a&gt;, rather than the site index page. Additionally, if a user requests a missing file, COS returns an &lt;a href="https://cloud.ibm.com/docs/services/cloud-object-storage?topic=cloud-object-storage-compatibility-common#compatibility-errors"&gt;XML error message&lt;/a&gt; rather than a custom error page.&lt;/p&gt;

&lt;p&gt;Both issues can be resolved using &lt;a href="https://www.ibm.com/cloud/blog/edge-computing-for-serverless-applications?mhsrc=ibmsearch_a&amp;amp;mhq=edge%20functions"&gt;Edge Functions&lt;/a&gt;, a new feature in Cloud Internet Services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Edge Functions
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://cloud.ibm.com/docs/infrastructure/cis?topic=cis-edge-functions"&gt;Edge functions&lt;/a&gt; are JavaScript source files deployed to Cloudflare's Edge locations. They can dynamically modify HTTP traffic passing through Cloudflare's network (for domains you control). Custom edge functions are triggered on configurable URL routes. Functions are passed the incoming HTTP request and control the HTTP response returned.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Edge Function to provide Index &amp;amp; Error Documents
&lt;/h3&gt;

&lt;p&gt;Using a custom edge function, HTTP traffic to the custom sub-domain can be modified to support Index and Error documents. Incoming HTTP requests without an explicit file name can be changed to use the index page location. HTTP 404 responses returned from COS can be replaced with a custom error page.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the "&lt;em&gt;Edge Functions&lt;/em&gt;" page from the Cloud Internet Services instance homepage.&lt;/li&gt;
&lt;li&gt;Click the "&lt;em&gt;Create&lt;/em&gt;" icon on the "&lt;em&gt;Actions&lt;/em&gt;" tab.&lt;/li&gt;
&lt;li&gt;Enter "&lt;em&gt;route-index-and-errors&lt;/em&gt;" in the  action name field.&lt;/li&gt;
&lt;li&gt;Paste the following &lt;a href="https://gist.github.com/jthomas/3c6c1db53e6f8ae7e70e2238b8c3374b"&gt;source code&lt;/a&gt; into the action body section.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The &lt;code&gt;INDEX_DOCUMENT&lt;/code&gt; and &lt;code&gt;ERROR_DOCUMENT&lt;/code&gt; values control the index and error pages used to redirect requests. Replace these values with the correct page locations for the static site being hosted.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;INDEX_DOCUMENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ERROR_DOCUMENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;404.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// if request is a directory path, append the index document.&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;INDEX_DOCUMENT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// if bucket file is missing, return error page.&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;    
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ERROR_DOCUMENT&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;statusText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not Found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;      
  &lt;span class="p"&gt;}&lt;/span&gt; 

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Click the "&lt;em&gt;Save&lt;/em&gt;" button.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Set up Triggers for Edge Function
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Select the "&lt;em&gt;Triggers&lt;/em&gt;" panel from the Edge Functions page.&lt;/li&gt;
&lt;li&gt;Click the "&lt;em&gt;Add trigger&lt;/em&gt;" icon.&lt;/li&gt;
&lt;li&gt;Set the Trigger URL to &lt;code&gt;http://&amp;lt;SUB_DOMAIN&amp;gt;.&amp;lt;CUSTOM_DOMAIN&amp;gt;/*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Select the "&lt;em&gt;route-index-and-errors&lt;/em&gt;" action from the drop-down menu.&lt;/li&gt;
&lt;li&gt;Click the "&lt;em&gt;Save&lt;/em&gt;" button.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Test Index and Error Pages
&lt;/h3&gt;

&lt;p&gt;Having set up the trigger and edge function, HTTP requests to the root path on the custom sub-domain will return the index page. Accessing invalid bucket files will also return the error page, rather than the COS error response.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confirm that &lt;code&gt;http://&amp;lt;SUB_DOMAIN&amp;gt;.&amp;lt;CUSTOM_DOMAIN&amp;gt;/&lt;/code&gt; returns the same page as &lt;code&gt;http://&amp;lt;SUB_DOMAIN&amp;gt;.&amp;lt;CUSTOM_DOMAIN&amp;gt;/index.html&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Confirm that &lt;code&gt;http://&amp;lt;SUB_DOMAIN&amp;gt;.&amp;lt;CUSTOM_DOMAIN&amp;gt;/missing-page.html&lt;/code&gt; returns the error page. This should be different to the XML error response returned by visiting &lt;code&gt;&amp;lt;BUCKET_NANME&amp;gt;.s3.&amp;lt;REGION&amp;gt;.cloud-object-storage.appdomain.cloud/missing-page.html&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;If this all works - the site is working! IBM Cloud is now hosting a static website using Cloud Object Storage and Cloud Internet Services with Page Rules and Edge Functions.&lt;/strong&gt; 🎉🎉🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Static web sites can be hosted on IBM Cloud using Cloud Object Storage and Cloud Internet Services. &lt;/p&gt;

&lt;p&gt;Cloud Object stores page files needed to render the static website. Anonymous bucket file access means files are accessible as public HTTP endpoints, without having to run infrastructure to serve the assets.&lt;/p&gt;

&lt;p&gt;Cloud Internet Services forwards HTTP traffic from a custom domain to the bucket hostname. DNS CNAME records are used to resolve the sub-domain as the custom bucket hostname. Page Rules override HTTP request headers to make this work. Edge Functions are used to implement configurable Index and Error documents, by dynamically modifying in-flight requests with custom JavaScript.&lt;/p&gt;

&lt;p&gt;Hosting static web sites using this method can be much cheaper (and easier) than traditional infrastructure. Developers only get charged for actual site usage, based on bandwidth and HTTP requests.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>web</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Connecting to IBM Cloud Databases for Redis from Node.js</title>
      <dc:creator>James Thomas</dc:creator>
      <pubDate>Mon, 22 Jul 2019 14:48:13 +0000</pubDate>
      <link>https://dev.to/ibmdeveloper/connecting-to-ibm-cloud-databases-for-redis-from-node-js-4h58</link>
      <guid>https://dev.to/ibmdeveloper/connecting-to-ibm-cloud-databases-for-redis-from-node-js-4h58</guid>
      <description>&lt;p&gt;This blog post explains how to connect to an &lt;a href="https://www.ibm.com/cloud/databases-for-redis" rel="noopener noreferrer"&gt;IBM Cloud Databases for Redis&lt;/a&gt; instance from a &lt;a href="https://nodejs.org/en/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; application. There is a (small) difference between the connection details needed for an IBM Cloud Databases for Redis instance compared to a local instance of the open-source database. This is due to all IBM Cloud Databases using &lt;a href="https://cloud.ibm.com/docs/services/databases-for-redis?topic=databases-for-redis-external-app#driver-tls-and-self-signed-certificate-support" rel="noopener noreferrer"&gt;secured TLS connections&lt;/a&gt; with &lt;a href="https://en.wikipedia.org/wiki/Self-signed_certificate" rel="noopener noreferrer"&gt;self-signed certificates&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I keep running into this issue (and forgetting how to fix it&lt;/em&gt; 🤦‍♂️&lt;em&gt;), so I'm documenting the solution here to help myself (and others) who might run into it…&lt;/em&gt; 🦸‍♂️&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting to Redis (without TLS connections)
&lt;/h2&gt;

&lt;p&gt;Most Node.js application use the &lt;code&gt;redis&lt;/code&gt; &lt;a href="https://www.npmjs.com/package/redis" rel="noopener noreferrer"&gt;NPM library&lt;/a&gt; to interact with an instance of the database. This library has a &lt;code&gt;createClient&lt;/code&gt; &lt;a href=""&gt;method&lt;/a&gt; which returns an instance of the client. The Node.js application passes a connection string into the &lt;code&gt;createClient&lt;/code&gt; method. This string contains the hostname, port, username and password for the database instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;redis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redis://user:secret@localhost:6379/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client fires a &lt;code&gt;connect&lt;/code&gt; event once the &lt;a href="https://github.com/NodeRedis/node_redis#connection-and-other-events" rel="noopener noreferrer"&gt;connection is established&lt;/a&gt; or an &lt;code&gt;error&lt;/code&gt; event if &lt;a href="https://github.com/NodeRedis/node_redis#connection-and-other-events" rel="noopener noreferrer"&gt;issues are encountered&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  IBM Cloud Databases for Redis Service Credentials
&lt;/h2&gt;

&lt;p&gt;IBM Cloud Databases for Redis provide service credentials through the &lt;a href="https://cloud.ibm.com/docs/services/databases-for-redis?topic=databases-for-redis-connection-strings#the-_service-credentials_-panel" rel="noopener noreferrer"&gt;instance management console&lt;/a&gt;. Service credentials are JSON objects with connection properties for client libraries, the CLI and other tools. Connection strings for the Node.js client library are available in the &lt;code&gt;connection.rediss.composed&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;So, I just copy this field value and use with the &lt;code&gt;redis.createClient&lt;/code&gt; method? Not so fast...&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;IBM Cloud Databases for Redis uses TLS to secure all connections to the Redis instances. This is denoted by the connection string using the &lt;code&gt;rediss://&lt;/code&gt; URL prefix, rather than &lt;code&gt;redis://&lt;/code&gt;. Using that connection string (without further connection properties), will lead to the following error being thrown by the Node.js application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Redis connection to &amp;lt;id&amp;gt;.databases.appdomain.cloud:port failed - read ECONNRESET
  at TCP.onread (net.js:657:25) errno: 'ECONNRESET', code: 'ECONNRESET', syscall: 'read'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the &lt;code&gt;createClient&lt;/code&gt; forces a TLS connection to be used &lt;code&gt;createClient(url, { tls: {} })&lt;/code&gt;, this error will be replaced with a different one about self-signed certificates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Redis connection to &amp;lt;id&amp;gt;.databases.appdomain.cloud:port failed failed - self signed certificate in certificate chain
    at TLSSocket.onConnectSecure (_tls_wrap.js:1055:34)
    at TLSSocket.emit (events.js:182:13)
    at TLSSocket._finishInit (_tls_wrap.js:635:8) code: 'SELF_SIGNED_CERT_IN_CHAIN'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Hmmmm, how to fix this?&lt;/em&gt; 🤔&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting to Redis (with TLS connections)
&lt;/h2&gt;

&lt;p&gt;All connections to IBM Cloud Databases are secured with TLS using self-signed certificates. Public certificates for the signing authorities are provided as Base64 strings in the service credentials. These certificates can be provided in the client constructor to support self-signed TLS connections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Here are the steps needed to use those self-signed certificates with the client library...&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extract the &lt;code&gt;connection.rediss.certificate.certificate_base64&lt;/code&gt; value from the service credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fecu7eduv9u6f343kkurn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fecu7eduv9u6f343kkurn.png" alt="Redis Service Credentials"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decode the Base64 string in Node.js to extract the PEM certificate string.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ca&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cert_base64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Provide the certificate file string as the &lt;code&gt;ca&lt;/code&gt; property in the &lt;code&gt;tls&lt;/code&gt; object for the client constructor.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ca&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tls&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;…Relax! 😎&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The &lt;code&gt;tls&lt;/code&gt; property is passed through to the &lt;code&gt;tls.connect&lt;/code&gt; &lt;a href="https://nodejs.org/api/tls.html#tls_tls_connect_options_callback" rel="noopener noreferrer"&gt;method&lt;/a&gt; in Node.js, which is used to setup the TLS connection. This method supports a &lt;code&gt;ca&lt;/code&gt; parameter to extend the trusted CA certificates pre-installed in the system. By providing the self-signed certificate using this property, the errors above will not be seen.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;It took me a while to &lt;a href="https://compose.com/articles/ssl-connections-arrive-for-redis-on-compose/" rel="noopener noreferrer"&gt;work out&lt;/a&gt; how to connect to TLS-secured Redis instances from a Node.js application. &lt;a href="https://stackoverflow.com/questions/10888610/ignore-invalid-self-signed-ssl-certificate-in-node-js-with-https-request/39099130#39099130" rel="noopener noreferrer"&gt;Providing the self-signed certificate&lt;/a&gt; in the client constructor is a much better solution than having to &lt;a href="https://stackoverflow.com/a/21961005/1427084" rel="noopener noreferrer"&gt;disable  all unauthorised TLS connections&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Since I don't write new Redis client code very often, I keep forgetting the correct constructor parameters to make this work. Turning this solution into a blog post will (hopefully) embed it in my brain (or at least provide a way to find the answer instead of having to grep through old project code). This might even be useful to others Googling for a solution to those error messages...&lt;/p&gt;

</description>
      <category>node</category>
      <category>redis</category>
      <category>ibmcloud</category>
    </item>
    <item>
      <title>Serverless APIs for Machine Learning models</title>
      <dc:creator>James Thomas</dc:creator>
      <pubDate>Wed, 03 Jul 2019 13:19:50 +0000</pubDate>
      <link>https://dev.to/ibmdeveloper/serverless-apis-for-machine-learning-models-43lk</link>
      <guid>https://dev.to/ibmdeveloper/serverless-apis-for-machine-learning-models-43lk</guid>
      <description>&lt;p&gt;IBM's &lt;a href="https://developer.ibm.com/exchanges/models/"&gt;Model Asset eXchange&lt;/a&gt; provides a &lt;a href="https://developer.ibm.com/exchanges/models/all/"&gt;curated list&lt;/a&gt; of free Machine Learning models for developers. Models currently published include detecting &lt;a href="https://developer.ibm.com/exchanges/models/all/max-facial-emotion-classifier/"&gt;emotions&lt;/a&gt; or &lt;a href="https://developer.ibm.com/exchanges/models/all/max-facial-age-estimator/"&gt;ages&lt;/a&gt; in faces from images, &lt;a href="https://developer.ibm.com/exchanges/models/all/max-weather-forecaster/"&gt;forecasting the weather&lt;/a&gt;, converting &lt;a href="https://developer.ibm.com/exchanges/models/all/max-speech-to-text-converter/"&gt;speech to text&lt;/a&gt; and more. Models are pre-trained and ready for use in the cloud.&lt;/p&gt;

&lt;p&gt;Models are published as series of &lt;a href="https://hub.docker.com/search?q=codait&amp;amp;type=image"&gt;public Docker images&lt;/a&gt;. Images automatically expose a HTTP API for model predictions. Documentation in the model repositories explains how to run images locally (using Docker) or deploy to the cloud (using Kubernetes). This got me thinking… &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Could MAX models be used from serverless functions?&lt;/strong&gt; 🤔&lt;/p&gt;

&lt;p&gt;Running machine learning models on serverless platforms can take advantage of the horizontal scalability to process large numbers of computationally intensive classification tasks in parallel. Coupled with the serverless pricing structure ("&lt;em&gt;no charge for idle&lt;/em&gt;"), this can be an extremely cheap and effective way to perform model classifications in the cloud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CHALLENGE ACCEPTED!&lt;/strong&gt; 🦸‍♂️🦸‍♀️&lt;/p&gt;

&lt;p&gt;After a couple days of experimentation, I had worked out an easy way to &lt;a href="https://github.com/jthomas/serverless-max-models"&gt;automatically expose MAX models as Serverless APIs&lt;/a&gt; on &lt;a href="https://cloud.ibm.com/openwhisk"&gt;IBM Cloud Functions&lt;/a&gt;.  🎉🎉🎉 &lt;/p&gt;

&lt;p&gt;&lt;em&gt;I've given instructions below on how to create those APIs from the models using a simple script. If you just want to use the models, follow those instructions. If you are interested in understanding how this works, keep reading as I explain afterwards what I did...&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Running MAX models on IBM Cloud Functions
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/jthomas/serverless-max-models"&gt;This repository&lt;/a&gt; contains a &lt;a href="https://github.com/jthomas/serverless-max-models/blob/master/build.sh"&gt;bash script&lt;/a&gt; which builds custom Docker runtimes with MAX models for usage on &lt;a href="https://cloud.ibm.com/openwhisk"&gt;IBM Cloud Functions&lt;/a&gt;. Pushing these images to Docker Hub allows IBM Cloud Functions to use them as &lt;a href="https://github.com/apache/incubator-openwhisk/blob/master/docs/actions-docker.md"&gt;custom runtimes&lt;/a&gt;. &lt;a href="https://github.com/apache/incubator-openwhisk/blob/master/docs/webactions.md"&gt;Web Actions&lt;/a&gt; created from these custom runtime images expose the same Prediction API described in the model documentation. They can be used with no further changes or custom code needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  prerequisites
&lt;/h3&gt;

&lt;p&gt;Please follow the links below to set up the following tools before proceeding.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/"&gt;Docker Hub account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.ibm.com/registration"&gt;IBM Cloud account&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.ibm.com/openwhisk/learn/cli"&gt;IBM Cloud Functions CLI installed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Check out the "&lt;a href="https://github.com/jthomas/serverless-max-models"&gt;Serverless MAX Models&lt;/a&gt; repository. Run all the following commands from that folder.&lt;/strong&gt;&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/jthomas/serverless-max-models 
cd serverless-max-models 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  build custom runtime images
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Set the following environment variables (&lt;code&gt;MODELS&lt;/code&gt;) with &lt;a href="https://hub.docker.com/search?q=codait&amp;amp;type=image"&gt;MAX model names&lt;/a&gt; and run build script.

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MODELS&lt;/code&gt;: MAX model names, e.g. &lt;code&gt;max-facial-emotion-classifier&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;USERNAME&lt;/code&gt;: Docker Hub username.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MODELS="..." USERNAME="..." ./build.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will create Docker images locally with the MAX model names and push to Docker Hub for usage in IBM Cloud Functions. &lt;strong&gt;IBM Cloud Functions only supports public Docker images as custom runtimes.&lt;/strong&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  create actions using custom runtimes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create a Web Action using the &lt;a href="https://github.com/apache/incubator-openwhisk/blob/master/docs/actions-docker.md"&gt;custom Docker runtime&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ibmcloud wsk action create &amp;lt;MODEL_IMAGE&amp;gt; --docker &amp;lt;DOCKERHUB_NAME&amp;gt;/&amp;lt;MODEL_IMAGE&amp;gt; --web true -m 512
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Retrieve the Web Action URL (&lt;code&gt;https://&amp;lt;REGION&amp;gt;.functions.cloud.ibm.com/api/v1/web/&amp;lt;NS&amp;gt;/default/&amp;lt;ACTION&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ibmcloud wsk action get &amp;lt;MODEL_IMAGE&amp;gt; --url
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  invoke web action url with prediction api parameters
&lt;/h3&gt;

&lt;p&gt;Use the same API request parameters as defined in the Prediction API specification with the Web Action URL. This will invoke model predictions and return the result as the HTTP response, e.g.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -F "image=@assets/happy-baby.jpeg" -XPOST &amp;lt;WEB_ACTION_URL&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;NOTE: The first invocation after creating an action may incur long cold-start delays due to the platform pulling the remote image into the local registry. Once the image is available in the platform, both further cold and warm invocations will be much faster.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Here is an example of creating a serverless API using the &lt;code&gt;max-facial-emotion-classifier&lt;/code&gt; &lt;a href="https://developer.ibm.com/exchanges/models/all/max-facial-emotion-classifier/"&gt;MAX model&lt;/a&gt;. Further examples of models which have been tested are available &lt;a href="https://github.com/jthomas/serverless-max-models/blob/master/README.md#models"&gt;here&lt;/a&gt;. If you encounter problems, please &lt;a href="https://github.com/jthomas/serverless-max-models/issues"&gt;open an issue&lt;/a&gt; on Github.&lt;/p&gt;

&lt;h3&gt;
  
  
  max-facial-emotion-classifier
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.ibm.com/exchanges/models/all/max-facial-emotion-classifier/"&gt;Facial Emotion Classifier (&lt;code&gt;max-facial-emotion-classifier&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start by creating the action using the custom runtime and then retrieve the Web Action URL.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ibmcloud wsk action create max-facial-emotion-classifier --docker &amp;lt;DOCKERHUB_NAME&amp;gt;/max-facial-emotion-classifier --web true -m 512
ok: created action max-facial-emotion-classifier
$ ibmcloud wsk action get max-facial-emotion-classifier --url
ok: got action max-facial-emotion-classifier
https://&amp;lt;REGION&amp;gt;.functions.cloud.ibm.com/api/v1/web/&amp;lt;NS&amp;gt;/default/max-facial-emotion-classifier
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;According to the &lt;a href="http://max-facial-emotion-classifier.max.us-south.containers.appdomain.cloud/"&gt;API definition&lt;/a&gt; for this model, the prediction API expects a form submission with an image file to classify. Using a &lt;a href="https://github.com/IBM/MAX-Facial-Emotion-Classifier/blob/master/assets/happy-baby.jpeg"&gt;sample image&lt;/a&gt; from the model repo, the model can be tested using curl.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -F "image=@happy-baby.jpeg" -XPOST https://&amp;lt;REGION&amp;gt;.functions.cloud.ibm.com/api/v1/web/&amp;lt;NS&amp;gt;/default/max-facial-emotion-classifier
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"predictions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"detection_box"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="mf"&gt;0.15102639296187684&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="mf"&gt;0.3828125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="mf"&gt;0.5293255131964809&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="mf"&gt;0.5830078125&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"emotion_predictions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"label_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"happiness"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"probability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.9860254526138306&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  performance
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Example Invocation Duration (Cold):&lt;/em&gt; ~4.8 seconds&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example Invocation Duration (Warm):&lt;/em&gt; ~ 800 ms&lt;/p&gt;

&lt;h2&gt;
  
  
  How does this work?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  background
&lt;/h3&gt;

&lt;p&gt;Running machine learning classifications using pre-trained models from serverless functions has historically been challenging due to the following reason… &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Developers do not control runtime environments in (most) serverless cloud platforms. Libraries and dependencies needed by the functions must be provided in the deployment package. Most platforms limit deployment package sizes (~50MB compressed &amp;amp; ~250MB uncompressed).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Machine Learning libraries and models can be much larger than those deployment size limits. This stops them being included in deployment packages. Loading files dynamically during invocations may be possible but incurs extremely long cold-start delays and additional costs.&lt;/p&gt;

&lt;p&gt;Fortunately, &lt;a href="https://cloud.ibm.com/openwhisk"&gt;IBM Cloud Functions&lt;/a&gt; is based on the open-source serverless project, &lt;a href="http://openwhisk.incubator.apache.org/"&gt;Apache OpenWhisk&lt;/a&gt;. This platform supports bespoke function runtimes using &lt;a href="https://github.com/apache/incubator-openwhisk/blob/master/docs/actions-docker.md"&gt;custom Docker images&lt;/a&gt;. Machine learning libraries and models can therefore be provided in custom runtimes. This removes the need to include them in deployment packages or be loaded at runtime.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Interested in reading other blog posts about using machine learning libraries and toolkits with IBM Cloud Functions? See &lt;a href="http://jamesthom.as/blog/2017/08/04/large-applications-on-openwhisk/"&gt;these posts&lt;/a&gt; for &lt;a href="http://jamesthom.as/blog/2018/08/13/serverless-machine-learning-with-tensorflow-dot-js/"&gt;more details&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  MAX model images
&lt;/h3&gt;

&lt;p&gt;IBM's &lt;a href="https://developer.ibm.com/exchanges/models/all/"&gt;Model Asset eXchange&lt;/a&gt; publishes Docker images for each model, alongside the pre-trained model files. Images expose a &lt;a href="https://github.com/IBM/MAX-Text-Sentiment-Classifier#3-use-the-model"&gt;HTTP API for predictions&lt;/a&gt; using the model on port 5000, built using Python and Flask. &lt;a href="http://max-text-sentiment-classifier.max.us-south.containers.appdomain.cloud/"&gt;Swagger files&lt;/a&gt; for the APIs describe the available operations, input parameters and response bodies.&lt;/p&gt;

&lt;p&gt;These images use a custom application framework (&lt;a href="https://pypi.org/project/maxfw/"&gt;maxfw&lt;/a&gt;), based on Flask, to standardise exposing MAX models as HTTP APIs. This framework handles input parameter validation, response marshalling, CORS support, etc. This allows model runtimes to just implement the prediction API handlers, rather than the entire HTTP application.&lt;/p&gt;

&lt;p&gt;Since the framework already handles exposing the model as a HTTP API, I started looking for a way to simulate an external HTTP request coming into the framework. If this was possible, I could trigger this fake request from a Python Web Action to perform the model classification from input parameters. The Web Action would then covert the HTTP response returned into the valid Web Action response parameters.&lt;/p&gt;

&lt;h3&gt;
  
  
  flask test client
&lt;/h3&gt;

&lt;p&gt;Reading through the Flask &lt;a href="http://flask.pocoo.org/docs/1.0/testing/"&gt;documentation&lt;/a&gt;, I came across the perfect solution! 👏👏👏&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Flask provides a way to test your application by exposing the Werkzeug test Client and handling the context locals for you. You can then use that with your favourite testing solution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This allows application routes to be executed with the &lt;a href="https://werkzeug.palletsprojects.com/en/0.15.x/test/#werkzeug.test.Client"&gt;test client&lt;/a&gt;, without actually running the HTTP server.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;max_app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MAXApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API_TITLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API_DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API_VERSION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;max_app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelPredictAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/predict'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;test_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max_app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;test_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/model/predict'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Using this code within a serverless Python function allows function invocations to trigger the prediction API.  The serverless function only has to convert input parameters to the fake HTTP request and then serialise the response back to JSON.&lt;/p&gt;

&lt;h3&gt;
  
  
  python docker action
&lt;/h3&gt;

&lt;p&gt;The custom MAX model runtime image needs to implement the &lt;a href="https://github.com/apache/incubator-openwhisk/blob/master/docs/actions-new.md#action-interface"&gt;HTTP API expected&lt;/a&gt; by Apache OpenWhisk. This API is used to instantiate the runtime environment and then pass in invocation parameters on each request. Since the runtime image contains all files and code need to process requests, the &lt;code&gt;/init&lt;/code&gt; handler becomes a &lt;a href="https://english.stackexchange.com/questions/25993/what-does-no-op-mean"&gt;no-op&lt;/a&gt;. The &lt;code&gt;/run&lt;/code&gt; handler converts &lt;a href="https://github.com/apache/incubator-openwhisk/blob/master/docs/webactions.md#http-context"&gt;Web Action HTTP parameters&lt;/a&gt; into the fake HTTP request.&lt;/p&gt;

&lt;p&gt;Here is the Python script used to proxy incoming Web Actions requests to the framework model service.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;maxfw.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MAXApp&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ModelPredictAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;config&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;API_TITLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API_DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API_VERSION&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;

&lt;span class="n"&gt;max_app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MAXApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API_TITLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API_DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API_VERSION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;max_app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelPredictAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/predict'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Use flask test client to simulate HTTP requests for the prediction APIs
# HTTP request data will come from action invocation parameters, neat huh? :)
&lt;/span&gt;&lt;span class="n"&gt;test_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max_app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# This implements the Docker runtime API used by Apache OpenWhisk
# https://github.com/apache/incubator-openwhisk/blob/master/docs/actions-docker.md
# /init is a no-op as everything is provided in the image.
&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/init"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;

&lt;span class="c1"&gt;# Action invocation requests will be received as the `value` parameter in request body.
# Web Actions provide HTTP request parameters as `__ow_headers` &amp;amp; `__ow_body` parameters.
&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;
    &lt;span class="n"&gt;form_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;'__ow_body'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;'__ow_headers'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# binary image content provided as base64 strings
&lt;/span&gt;    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form_body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# send fake HTTP request to prediction API with invocation data
&lt;/span&gt;    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;test_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/model/predict'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r_headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# binary data must be encoded as base64 strings to return in JSON response
&lt;/span&gt;    &lt;span class="n"&gt;is_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r_headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_image&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r_data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'headers'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r_headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'body'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mimetype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'0.0.0.0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  building into an image
&lt;/h3&gt;

&lt;p&gt;Since the MAX models already exist as public Docker images, those images can be used as base images when building custom runtimes. Those base images handle adding model files and all dependencies needed to execute them into the image.&lt;/p&gt;

&lt;p&gt;This is the &lt;code&gt;Dockerfile&lt;/code&gt; used by the build script to create the custom model image. The &lt;code&gt;model&lt;/code&gt; parameter refers to the build argument containing the model name.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ARG model
FROM codait/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;:latest

ADD openwhisk.py &lt;span class="nb"&gt;.&lt;/span&gt;

EXPOSE 8080

CMD python openwhisk.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is then used from the following build script to create a custom runtime image for the model.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;model &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$MODELS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Building &lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="s2"&gt; runtime image"&lt;/span&gt;
  docker build &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$model&lt;/span&gt; &lt;span class="nt"&gt;--build-arg&lt;/span&gt; &lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Pushing &lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="s2"&gt; to Docker Hub"&lt;/span&gt;
  docker tag &lt;span class="nv"&gt;$model&lt;/span&gt; &lt;span class="nv"&gt;$USERNAME&lt;/span&gt;/&lt;span class="nv"&gt;$model&lt;/span&gt;
  docker push &lt;span class="nv"&gt;$USERNAME&lt;/span&gt;/&lt;span class="nv"&gt;$model&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once the image is published to Docker Hub, it can be referenced when creating new Web Actions (using the &lt;code&gt;—docker&lt;/code&gt; parameter). 😎&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ibmcloud wsk action create &amp;lt;MODEL_IMAGE&amp;gt; --docker &amp;lt;DOCKERHUB_NAME&amp;gt;/&amp;lt;MODEL_IMAGE&amp;gt; --web true -m 512
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;IBM's Model Asset eXchange is a curated collection of Machine Learning models, ready to deploy to the cloud for a variety of tasks. All models are available as a series of public Docker images. Models images automatically expose HTTP APIs for classifications.&lt;/p&gt;

&lt;p&gt;Documentation in the model repositories explains how to run them locally and deploy using Kubernetes, but what about using on serverless cloud platforms? Serverless platforms are becoming a popular option for deploying Machine Learning models, due to horizontal scalability and cost advantages.&lt;/p&gt;

&lt;p&gt;Looking through the source code for the model images, I discovered a mechanism to hook into the custom model framework used to export the model files as HTTP APIs. This allowed me write a simple wrapper script to proxy serverless function invocations to the model prediction APIs. API responses would be serialised back into the Web Action response format. &lt;/p&gt;

&lt;p&gt;Building this script into a new Docker image, using the existing model image as the base image, created a new runtime which could be used on the platform. Web Actions created from this runtime image would automatically expose the same HTTP APIs as the existing image!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>machinelearning</category>
      <category>python</category>
      <category>openwhisk</category>
    </item>
    <item>
      <title>Saving Money and Time With Node.js Worker Threads in Serverless Functions</title>
      <dc:creator>James Thomas</dc:creator>
      <pubDate>Fri, 10 May 2019 15:22:49 +0000</pubDate>
      <link>https://dev.to/ibmdeveloper/saving-money-and-time-with-node-js-worker-threads-in-serverless-functions-d0n</link>
      <guid>https://dev.to/ibmdeveloper/saving-money-and-time-with-node-js-worker-threads-in-serverless-functions-d0n</guid>
      <description>&lt;p&gt;Node.js v12 was &lt;a href="https://foundation.nodejs.org/announcements/2019/04/24/node-js-foundation-and-js-foundation-merge-to-form-openjs-foundation-2"&gt;released last month&lt;/a&gt;. This new version includes support for &lt;a href="https://nodejs.org/api/worker_threads.html"&gt;Worker Threads&lt;/a&gt;, that are enabled by default. Node.js &lt;a href="https://nodejs.org/api/worker_threads.html"&gt;Worker Threads&lt;/a&gt; make it simple to execute JavaScript code in parallel using threads. 👏👏👏&lt;/p&gt;

&lt;p&gt;This is useful for Node.js applications with CPU-intensive workloads. Using Worker Threads, JavaScript code can be executed code concurrently using multiple CPU cores. This reduces execution time compared to a non-Worker Threads version.&lt;/p&gt;

&lt;p&gt;If serverless platforms provide Node.js v12 on multi-core environments, functions can use this feature to reduce execution time and, therefore, lower costs. Depending on the workload, functions can utilise all available CPU cores to parallelise work, rather than executing more functions concurrently. 💰💰💰&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this blog post, I'll explain how to use Worker Threads from a serverless function. I'll be using &lt;a href="https://cloud.ibm.com/openwhisk"&gt;IBM Cloud Functions&lt;/a&gt; (&lt;a href="http://openwhisk.incubator.apache.org/"&gt;Apache OpenWhisk&lt;/a&gt;) as the example platform but this approach is applicable for any serverless platform with Node.js v12 support and a multi-core CPU runtime environment.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Node.js v12 in IBM Cloud Functions (Apache OpenWhisk)
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;This section of the blog post is specifically about using the new &lt;a href="https://github.com/apache/incubator-openwhisk-runtime-nodejs"&gt;Node.js v12 runtime&lt;/a&gt; on IBM Cloud Functions (powered by &lt;a href="http://openwhisk.incubator.apache.org/"&gt;Apache OpenWhisk&lt;/a&gt;). If you are using a different serverless platform, feel free to skip ahead to the next section…&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;I've recently &lt;a href="https://github.com/apache/incubator-openwhisk-runtime-nodejs/pull/126"&gt;been working&lt;/a&gt; on adding the Node.js v12 runtime to Apache OpenWhisk.&lt;/p&gt;

&lt;p&gt;Apache OpenWhisk uses &lt;a href="https://hub.docker.com/u/openwhisk"&gt;Docker containers&lt;/a&gt; as runtime environments for serverless functions. All runtime images are maintained in separate repositories for each supported language, e.g. &lt;a href="https://github.com/apache/incubator-openwhisk-runtime-nodejs"&gt;Node.js&lt;/a&gt;, &lt;a href="https://github.com/apache/incubator-openwhisk-runtime-java"&gt;Java&lt;/a&gt;, &lt;a href="https://github.com/apache/incubator-openwhisk-runtime-python"&gt;Python&lt;/a&gt;, etc. Runtime images are automatically built and pushed to &lt;a href="https://hub.docker.com/r/openwhisk/"&gt;Docker Hub&lt;/a&gt; when the repository is updated.&lt;/p&gt;

&lt;h3&gt;
  
  
  node.js v12 runtime image
&lt;/h3&gt;

&lt;p&gt;Here is &lt;a href="https://github.com/apache/incubator-openwhisk-runtime-nodejs/pull/126"&gt;the PR&lt;/a&gt; used to add the new Node.js v12 runtime image to Apache OpenWhisk. This led to the following &lt;a href="https://hub.docker.com/r/openwhisk/action-nodejs-v12"&gt;runtime image&lt;/a&gt; being exported to Docker Hub: &lt;code&gt;openwhisk/action-nodejs-v12&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Having this image available as a native runtime in Apache OpenWhisk requires &lt;a href="https://github.com/apache/incubator-openwhisk/pull/4472"&gt;upstream changes&lt;/a&gt; to the project's runtime manifest. After this happens, developers will be able to use the &lt;code&gt;--kind&lt;/code&gt; CLI flag to select this runtime version.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ibmcloud wsk action create action_name action.js --kind nodejs:12
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="http://cloud.ibm.com/openwhisk"&gt;IBM Cloud Functions&lt;/a&gt; is powered by &lt;a href="http://openwhisk.incubator.apache.org/"&gt;Apache OpenWhisk&lt;/a&gt;. It will eventually pick up the upstream project changes to include this new runtime version. Until that happens, Docker support allows usage of this new runtime before it is built-in the platform.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ibmcloud wsk action create action_name action.js --docker openwhisk/action-nodejs-v12
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  example
&lt;/h3&gt;

&lt;p&gt;This Apache OpenWhisk action returns the version of Node.js used in the runtime environment.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Running this code on IBM Cloud Functions, using the Node.js v12 runtime image, allows us to confirm the new Node.js version is available.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ibmcloud wsk action create nodejs-v12 action.js --docker openwhisk/action-nodejs-v12
ok: created action nodejs-v12
$ ibmcloud wsk action invoke nodejs-v12 --result
{
    "version": "v12.1.0"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Worker Threads in Serverless Functions
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://medium.com/@Trott/using-worker-threads-in-node-js-80494136dbb6"&gt;This is a great introduction blog post&lt;/a&gt; to Workers Threads. It uses an example of generating prime numbers as the CPU intensive task to benchmark. Comparing the performance of the single-threaded version to multiple-threads - the performance is improved as a factor of the threads used (up to the number of CPU cores available).&lt;/p&gt;

&lt;p&gt;This code can be ported to run in a serverless function. Running with different input values and thread counts will allow benchmarking of the performance improvement.&lt;/p&gt;

&lt;h3&gt;
  
  
  non-workers version
&lt;/h3&gt;

&lt;p&gt;Here is the &lt;a href="https://gist.github.com/jthomas/71c76d62ddfd146c4bf763f5b2f0eec1"&gt;sample code&lt;/a&gt; for a serverless function to generate prime numbers. It does not use Worker Threads. It will run on the main &lt;a href="https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/"&gt;event loop&lt;/a&gt; for the Node.js process. This means it will only utilise a single thread (and therefore single CPU core).&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s1"&gt;'use strict'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;primes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isPrime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;isPrime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isPrime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;primes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;isPrime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;primes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  porting the code to use worker threads
&lt;/h3&gt;

&lt;p&gt;Here is the prime number calculation code which uses Worker Threads. Dividing the total input range by the number of Worker Threads generates individual thread input values. Worker Threads are spawned and passed chunked input ranges. Threads calculate primes and then send the result back to the parent thread.&lt;/p&gt;

&lt;p&gt;Reviewing the code to start converting it to a serverless function, I realised there were two issues running this code in serverless environment: &lt;strong&gt;worker thread initialisation&lt;/strong&gt; and &lt;strong&gt;optimal worker thread counts&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to initialise Worker Threads?
&lt;/h4&gt;

&lt;p&gt;This is how the existing source code &lt;a href="https://nodejs.org/dist/latest-v12.x/docs/api/worker_threads.html#worker_threads_new_worker_filename_options"&gt;initialises the Worker Threads&lt;/a&gt;.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt; &lt;span class="nx"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;workerData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt; &lt;span class="p"&gt;}}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;code&gt;__filename&lt;/code&gt; is a special global variable in Node.js which contains the currently executing script file path.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This means the Worker Thread will be initialised with a copy of the currently executing script. Node.js provides a special variable to indicate whether the script is executing in the parent or child thread. This can be used to branch script logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So, what's the issue with this?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the Apache OpenWhisk Node.js runtime, action source files are &lt;a href="https://github.com/apache/incubator-openwhisk-runtime-nodejs/blob/master/core/nodejsActionBase/runner.js#L61-L79"&gt;dynamically imported&lt;/a&gt; into the runtime environment. The script used to start the Node.js runtime process is for the &lt;a href="https://github.com/apache/incubator-openwhisk-runtime-nodejs/blob/master/core/nodejsActionBase/runner.js"&gt;platform handler&lt;/a&gt;, not the action source files. This means the &lt;code&gt;__filename&lt;/code&gt; variable does not point to the action source file.&lt;/p&gt;

&lt;p&gt;This issue is fixed by separating the serverless function handler and worker thread code into separate files. Worker Threads can be started with a reference to the worker thread script source file, rather than the currently executing script name.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt; &lt;span class="nx"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"./worker.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;workerData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt; &lt;span class="p"&gt;}}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  How Many Worker Threads?
&lt;/h4&gt;

&lt;p&gt;The next issue to resolve is how many Worker Threads to use. In order to maximise parallel processing capacity, there should be a Worker Thread for each CPU core. This is the maximum number of threads that can run concurrently.&lt;/p&gt;

&lt;p&gt;Node.js provides CPU information for the runtime environment using the &lt;code&gt;os.cpus()&lt;/code&gt; &lt;a href="https://nodejs.org/api/os.html#os_os_cpus"&gt;function&lt;/a&gt;. The result is an array of objects (one per logical CPU core), with model information, processing speed and elapsed processing times. The length of this array will determine number of Worker Threads used. This ensures the number of Worker Threads will always match the CPU cores available.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;threadCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cpus&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  workers threads version
&lt;/h3&gt;

&lt;p&gt;Here is the serverless version of the prime number generation algorithm which uses Worker Threads.&lt;/p&gt;

&lt;p&gt;The code is split over two files - &lt;code&gt;primes-with-workers.js&lt;/code&gt; and &lt;code&gt;worker.js&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  primes-with-workers.js
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/jthomas/154a039d52b97d5ed19d4ddac3ff9f43"&gt;This file&lt;/a&gt; contains the serverless function handler used by the platform. Input ranges (based on the &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt; action parameters) are divided into chunks, based upon the number of Worker Threads. The handler function creates a Worker Thread for each chunk and waits for the message with the result. Once all the results have been retrieved, it returns all those primes numbers as the invocation result.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s1"&gt;'use strict'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Worker&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'worker_threads'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'os'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;threadCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cpus&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;compute_primes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;primes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`adding worker (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; =&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'./worker.js'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;workerData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt; &lt;span class="p"&gt;}})&lt;/span&gt;

    &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'exit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;primes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;primes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;primes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;threadCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Calculating primes with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;threadCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; threads...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;threadCount&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;
    &lt;span class="nx"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compute_primes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compute_primes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;primes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;primes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;primes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  workers.js
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/jthomas/154a039d52b97d5ed19d4ddac3ff9f43#file-workers-js"&gt;This is the script&lt;/a&gt; used in the Worker Thread. The &lt;code&gt;workerData&lt;/code&gt; value is used to receive number ranges to search for prime numbers. Primes numbers are sent back to the parent thread using the &lt;code&gt;postMessage&lt;/code&gt; function. Since this script is only used in the Worker Thread, it does need to use the &lt;code&gt;isMainThread&lt;/code&gt; value to check if it is a child or parent process.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s1"&gt;'use strict'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isMainThread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parentPort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;workerData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'worker_threads'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;generatePrimes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;primes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isPrime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;isPrime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isPrime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;primes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;isPrime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;primes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;primes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;generatePrimes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workerData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;workerData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;parentPort&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;primes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  package.json
&lt;/h4&gt;

&lt;p&gt;Source files deployed from a zip file also need to include a &lt;code&gt;package.json&lt;/code&gt; file in the archive. The &lt;code&gt;main&lt;/code&gt; property is used to determine the script to import as the exported package module.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"worker_threads"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"primes-with-workers.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Comparison
&lt;/h2&gt;

&lt;p&gt;Running both functions with the same input parameters allows execution time comparison. The Worker Threads version should improve performance by a factor proportional to available CPU cores. Reducing execution time also means reduced costs in a serverless platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  non-workers performance
&lt;/h3&gt;

&lt;p&gt;Creating a new serverless function (&lt;code&gt;primes&lt;/code&gt;) from the non-worker threads source code, using the Node.js v12 runtime, I can test with small values to check correctness.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ibmcloud wsk action create primes primes.js &lt;span class="nt"&gt;--docker&lt;/span&gt; openwhisk/action-nodejs-v12
ok: created action primes
&lt;span class="nv"&gt;$ &lt;/span&gt;ibmcloud wsk action invoke primes &lt;span class="nt"&gt;--result&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; start 2 &lt;span class="nt"&gt;-p&lt;/span&gt; end 10
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"primes"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt; 2, 3, 5, 7 &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Playing with sample input values, 10,000,000 seems like a useful benchmark value. This takes long enough with the single-threaded version to benefit from parallelism.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;ibmcloud wsk action invoke primes &lt;span class="nt"&gt;--result&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; start 2 &lt;span class="nt"&gt;-p&lt;/span&gt; end 10000000 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null

real    0m35.151s
user    0m0.840s
sys 0m0.315s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Using the simple single-threaded algorithm it takes the serverless function around ~35 seconds to calculate primes up to ten million.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  workers threads performance
&lt;/h3&gt;

&lt;p&gt;Creating a new serverless function, from the worker threads-based source code using the Node.js v12 runtime, allows me to verify it works as expected for small input values.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ibmcloud wsk action create primes-workers action.zip --docker openwhisk/action-nodejs-v12
ok: created action primes-workers
$ ibmcloud wsk action invoke primes-workers --result -p min 2 -p max 10
{
    "primes": [ 2, 3, 5, 7 ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Hurrah, it works.&lt;/p&gt;

&lt;p&gt;Invoking the function with an &lt;code&gt;max&lt;/code&gt; parameter of 10,000,000 allows us to benchmark against the non-workers version of the code.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;ibmcloud wsk action invoke primes-workers &lt;span class="nt"&gt;--result&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; min 2 &lt;span class="nt"&gt;-p&lt;/span&gt; max 10000000 &lt;span class="nt"&gt;--result&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null

real    0m8.863s
user    0m0.804s
sys 0m0.302s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The workers versions only takes ~25% of the time of the single-threaded version!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is because IBM Cloud Functions' runtime environments provide access to four CPU cores. Unlike other platforms, CPU cores are not tied to memory allocations. Utilising all available CPU cores concurrently allows the algorithm to run 4x times as fast. Since serverless platforms charge based on execution time, reducing execution time also means reducing costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The worker threads version also costs 75% less than the single-threaded version!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://foundation.nodejs.org/announcements/2019/04/24/node-js-foundation-and-js-foundation-merge-to-form-openjs-foundation-2"&gt;Node.js v12&lt;/a&gt; was released in April 2019. This version included support for &lt;a href="https://nodejs.org/api/worker_threads.html"&gt;Worker Threads&lt;/a&gt;, that were enabled by default (rather than needing an optional runtime flag). Using multiple CPU cores in Node.js applications has never been easier!&lt;/p&gt;

&lt;p&gt;Node.js applications with CPU-intensive workloads can utilise this feature to reduce execution time. Since serverless platforms charge based upon execution time, this is especially useful for Node.js serverless functions. Utilising multiple CPU cores leads, not only to improved performance, but also lower bills.&lt;/p&gt;

&lt;p&gt;PRs have been &lt;a href="https://github.com/apache/incubator-openwhisk/pull/4472"&gt;opened&lt;/a&gt; to enable Node.js v12 as a built-in runtime to the Apache OpenWhisk project. This Docker &lt;a href="https://hub.docker.com/r/openwhisk/action-nodejs-v12"&gt;image&lt;/a&gt; for the new runtime version is already available on Docker Hub. This means it can be used with any Apache OpenWhisk instance straight away!&lt;/p&gt;

&lt;p&gt;Playing with Worker Threads on IBM Cloud Functions allowed me to demonstrate how to speed up performance for CPU-intensive workloads by utilising multiple cores concurrently. Using &lt;a href="https://gist.github.com/jthomas/154a039d52b97d5ed19d4ddac3ff9f43"&gt;an example of prime number generation&lt;/a&gt;, calculating all primes up to ten million took ~35 seconds with a single thread and ~8 seconds with four threads. This represents a reduction in execution time and cost of 75%!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>node</category>
      <category>openwhisk</category>
      <category>ibm</category>
    </item>
    <item>
      <title>Serverless CI/CD with Travis CI, Serverless Framework and IBM Cloud Functions</title>
      <dc:creator>James Thomas</dc:creator>
      <pubDate>Tue, 07 May 2019 10:34:46 +0000</pubDate>
      <link>https://dev.to/ibmdeveloper/serverless-ci-cd-with-travis-ci-serverless-framework-and-ibm-cloud-functions-4laj</link>
      <guid>https://dev.to/ibmdeveloper/serverless-ci-cd-with-travis-ci-serverless-framework-and-ibm-cloud-functions-4laj</guid>
      <description>&lt;p&gt;How do you set up a &lt;a href="https://dzone.com/articles/what-is-cicd"&gt;CI/CD pipeline&lt;/a&gt; for serverless applications? &lt;/p&gt;

&lt;p&gt;This blog post will explain how to use &lt;a href="https://travis-ci.org/"&gt;Travis CI&lt;/a&gt;, &lt;a href="https://github.com/serverless/serverless"&gt;The Serverless Framework&lt;/a&gt; and the &lt;a href="https://github.com/avajs/ava"&gt;AVA testing framework&lt;/a&gt; to set up a fully-automated build, deploy and test pipeline for a serverless application. It will use a real example of a production &lt;a href="https://github.com/jthomas/openwhisk-release-verification"&gt;serverless application&lt;/a&gt;, built using &lt;a href="http://openwhisk.incubator.apache.org/"&gt;Apache OpenWhisk&lt;/a&gt; and running on &lt;a href="https://console.bluemix.net/openwhisk/"&gt;IBM Cloud Functions&lt;/a&gt;. The CI/CD pipeline will execute the following tasks...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Run project unit tests.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy application to test environment.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run acceptance tests against test environment.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy application to production environment.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run smoke tests against production environment.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before diving into the details of the CI/CD pipeline setup, let's start by showing the example serverless application being used for this project...&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless Project - &lt;a href="http://apache.jamesthom.as/"&gt;http://apache.jamesthom.as/&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The "&lt;a href="https://github.com/jthomas/openwhisk-release-verification"&gt;Apache OpenWhisk Release Verification&lt;/a&gt;" project is a serverless web application to help committers verify release candidates for the open-source project. It automates running the verification steps from the &lt;a href="https://cwiki.apache.org/confluence/display/OPENWHISK/How+to+verify+the+release+checklist+and+vote+on+OpenWhisk+modules+under+Apache"&gt;ASF release checklist&lt;/a&gt; using serverless functions. Automating release candidate validation makes it easier for  committers to participate in release voting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N1V705yX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://raw.githubusercontent.com/jthomas/openwhisk-release-verification/master/release-verification-tool.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N1V705yX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://raw.githubusercontent.com/jthomas/openwhisk-release-verification/master/release-verification-tool.gif" alt="Apache OpenWhisk Release Verification Tool"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project consists of a static web assets (HTML, JS, CSS files) and HTTP APIs. Static web assets are hosted by Github Pages from the &lt;a href="https://github.com/jthomas/openwhisk-release-verification"&gt;project repository&lt;/a&gt;. HTTP APIs are implemented as Apache OpenWhisk &lt;a href="https://github.com/apache/incubator-openwhisk/blob/master/docs/actions.md"&gt;actions&lt;/a&gt; and exposed using the &lt;a href="https://github.com/apache/incubator-openwhisk/blob/master/docs/apigateway.md"&gt;API Gateway&lt;/a&gt; service. &lt;a href="https://console.bluemix.net/openwhisk/"&gt;IBM Cloud Functions&lt;/a&gt; is used to host the Apache OpenWhisk application.&lt;/p&gt;

&lt;p&gt;No other cloud services, like databases, are needed by the backend. Release candidate information is retrieved in real-time by parsing the &lt;a href="https://dist.apache.org/repos/dist/dev/incubator/openwhisk/"&gt;HTML page&lt;/a&gt; from the ASF website.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OoEH3o0V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://jamesthom.as/images/ow_release_verifier/architecture.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OoEH3o0V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://jamesthom.as/images/ow_release_verifier/architecture.png" alt="Serverless Architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/serverless/serverless"&gt;The Serverless Framework&lt;/a&gt; (with the &lt;a href="https://github.com/serverless/serverless-openwhisk"&gt;Apache OpenWhisk provider plugin&lt;/a&gt;) is used to define the serverless functions used in the application. HTTP endpoints are also defined in the YAML configuration file.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release-verfication&lt;/span&gt;

&lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openwhisk&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodejs:10&lt;/span&gt;

&lt;span class="na"&gt;functions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;versions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;index.versions&lt;/span&gt;
    &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET /api/versions&lt;/span&gt;
  &lt;span class="na"&gt;version_files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;index.version_files&lt;/span&gt;
    &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/api/versions/{version}&lt;/span&gt;
          &lt;span class="na"&gt;resp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;

&lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;serverless-openwhisk&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The framework handles all deployment and configuration tasks for the application. Setting up the application in a new environment is as simple as running the &lt;code&gt;serverless deploy&lt;/code&gt; &lt;a href="https://github.com/serverless/serverless"&gt;command&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Environments
&lt;/h3&gt;

&lt;p&gt;Apache OpenWhisk uses &lt;a href="https://github.com/apache/incubator-openwhisk/blob/master/docs/reference.md#fully-qualified-names"&gt;namespaces&lt;/a&gt; to group individual packages, actions, triggers and rules. Different namespaces can be used to provide isolated environments for applications.&lt;/p&gt;

&lt;p&gt;IBM Cloud Functions automatically creates &lt;a href="https://cloud.ibm.com/docs/openwhisk?topic=cloud-functions-cloudfunctions_cli#region_info"&gt;user-based namespaces&lt;/a&gt; in platform instances. These auto-generated namespaces mirror the IBM Cloud organisation and space used to access the instance. Creating &lt;a href="https://cloud.ibm.com/docs/account?topic=account-orgsspacesusers#cf-org-concepts"&gt;new spaces within an organisation&lt;/a&gt; will provision extra namespaces.&lt;/p&gt;

&lt;p&gt;I'm using a custom organisation for the application with three different spaces: &lt;strong&gt;dev&lt;/strong&gt;, &lt;strong&gt;test&lt;/strong&gt; and &lt;strong&gt;prod&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;dev&lt;/strong&gt; is used as a test environment to deploy functions during development. &lt;strong&gt;test&lt;/strong&gt; is used by the CI/CD pipeline to deploy a temporary instance of the application during acceptance tests. &lt;strong&gt;prod&lt;/strong&gt; is the production environment hosting the external application actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Credentials
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://cloud.ibm.com/docs/cli?topic=cloud-cli-install-ibmcloud-cli"&gt;IBM Cloud CLI&lt;/a&gt; is used to handle IBM Cloud Functions credentials. &lt;a href="https://cloud.ibm.com/docs/iam?topic=iam-manapikey"&gt;Platform API keys&lt;/a&gt; will be used to log in the CLI from the CI/CD system.&lt;/p&gt;

&lt;p&gt;When Cloud Functions CLI commands are issued (after targeting a new region, organisation or space), API keys for that Cloud Functions instance are automatically retrieved and stored locally. The Serverless Framework knows how to use these local credentials when interacting with the platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  High Availability?
&lt;/h3&gt;

&lt;p&gt;The Apache OpenWhisk Release Verifier is not a critical cloud application which needs "&lt;a href="https://en.wikipedia.org/wiki/Five_nines"&gt;five nines&lt;/a&gt;" of availability. The application is idle most of the time. It does not need a &lt;a href="https://en.wikipedia.org/wiki/High_availability"&gt;highly available&lt;/a&gt; serverless architecture. This means the build pipeline does not have to...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.ibm.com/docs/tutorials?topic=solution-tutorials-multi-region-serverless#multi-region-serverless"&gt;Deploy application instances in multiple cloud regions.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ibm.com/blogs/bluemix/2019/04/load-balancing-api-calls-across-regions-with-ibm-cloud-internet-services-and-cloud-api-gateway/"&gt;Set up a global load balancer between regional instances.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Support "&lt;a href="https://www.martinfowler.com/bliki/BlueGreenDeployment.html"&gt;zero downtime deploys&lt;/a&gt;" to minimise downtime during deployments.&lt;/li&gt;
&lt;li&gt;Automatic roll-back to previous versions on production issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;New deployments will simply overwrite resources in the production namespace in a single region. If the production site is broken after a deployment, the smoke tests should catch this and email me to fix it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;Given this tool will be used to check release candidates for the open-source project, I wanted to ensure it worked properly! Incorrect validation results could lead to invalid source archives being published.&lt;/p&gt;

&lt;p&gt;I've chosen to rely heavily on unit tests to check the core business logic. These tests ensure all validation tasks work correctly, including PGP signature verification, cryptographic hash matching, LICENSE file contents and other ASF requirements for project releases.&lt;/p&gt;

&lt;p&gt;Additionally, I've used end-to-end acceptance tests to validate the HTTP APIs work as expected. HTTP requests are sent to the API GW endpoints, with responses compared against expected values. All available release candidates are run through the validation process to check no errors are returned.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit Tests
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Unit_testing"&gt;Unit tests&lt;/a&gt; are implemented with the &lt;a href="https://github.com/avajs/ava"&gt;AVA testing framework&lt;/a&gt;. Unit tests live in the &lt;code&gt;unit/test/&lt;/code&gt; &lt;a href="https://github.com/jthomas/openwhisk-release-verification/tree/master/test/unit"&gt;folder&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;npm test&lt;/code&gt; command alias runs the &lt;code&gt;ava test/unit/&lt;/code&gt; command to execute all unit tests. This command can be executed locally, during development, or from the CI/CD pipeline.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;test&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; release-verification@1.0.0 &lt;span class="nb"&gt;test&lt;/span&gt; ~/code/release-verification
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ava &lt;span class="nb"&gt;test&lt;/span&gt;/unit/

 27 tests passed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Acceptance Tests
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Acceptance_testing"&gt;Acceptance tests&lt;/a&gt; check API endpoints return the expected responses for valid (and invalid) requests. Acceptance tests are executed against the API Gateway endpoints for an application instance.&lt;/p&gt;

&lt;p&gt;The hostname used for HTTP requests is controlled using an environment variable (&lt;code&gt;HOST&lt;/code&gt;). Since the same test suite test is used for acceptance and smoke tests, setting this environment variable is the only configuration needed to run tests against different environments.&lt;/p&gt;

&lt;p&gt;API endpoints in the test and production environments are exposed using different custom sub-domains (&lt;code&gt;apache-api.jamesthom.as&lt;/code&gt; and &lt;code&gt;apache-api-test.jamesthom.as&lt;/code&gt;). NPM &lt;a href="https://github.com/jthomas/openwhisk-release-verification/blob/master/package.json#L8-L9"&gt;scripts are used&lt;/a&gt; to provide commands (&lt;code&gt;acceptance-test&lt;/code&gt; &amp;amp; &lt;code&gt;acceptance-prod&lt;/code&gt;) which set the environment hostname before running the test suite.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"acceptance-test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"HOST=apache-api-test.jamesthom.as ava -v --fail-fast test/acceptance/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"acceptance-prod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"HOST=apache-api.jamesthom.as ava -v --fail-fast test/acceptance/"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm run acceptance-prod

&amp;gt; release-verification@1.0.0 acceptance-prod ~/code/release-verification
&amp;gt; HOST=apache-api.jamesthom.as ava -v --fail-fast  test/acceptance/

  ✔ should return list of release candidates (3.7s)
    ℹ running api testing against https://apache-api.jamesthom.as/api/versions
  ✔ should return 404 for file list when release candidate is invalid (2.1s)
    ℹ running api testing against https://apache-api.jamesthom.as/api/versions/unknown
  ...

  6 tests passed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Acceptance tests are also implemented with the AVA testing framework. All acceptance tests live in a single &lt;a href="https://github.com/jthomas/openwhisk-release-verification/blob/master/test/acceptance/api.js"&gt;test file&lt;/a&gt; (&lt;code&gt;unit/acceptance/api.js&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD Pipeline
&lt;/h2&gt;

&lt;p&gt;When new commits are pushed to the &lt;code&gt;master&lt;/code&gt; branch on the project repository, the following steps needed to be kicked off by the build pipeline…&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Run project unit tests.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Deploy application to test environment.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Run acceptance tests against test environment.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Deploy application to production environment.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Run smoke tests against production environment.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of the steps fail, the build pipeline should stop and send me a notification email.&lt;/p&gt;

&lt;h3&gt;
  
  
  Travis
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://travis-ci.org/"&gt;Travis CI&lt;/a&gt; is used to implement the CI/CD build pipeline. Travis CI uses a &lt;a href="https://github.com/jthomas/openwhisk-release-verification/blob/master/.travis.yml"&gt;custom file&lt;/a&gt; (&lt;code&gt;.travis.yml&lt;/code&gt;) in the project repository to configure the build pipeline. This YAML file defines commands to execute during each phase of build pipeline. If any of the commands fail, the build will stop at that phase without proceeding.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Here is the completed &lt;code&gt;.travis.yml&lt;/code&gt; file for this project: &lt;a href="https://github.com/jthomas/openwhisk-release-verification/blob/master/.travis.yml"&gt;https://github.com/jthomas/openwhisk-release-verification/blob/master/.travis.yml&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm using the following Travis CI &lt;a href="https://docs.travis-ci.com/user/job-lifecycle#the-job-lifecycle"&gt;build phases&lt;/a&gt; to implement the pipeline: &lt;strong&gt;install&lt;/strong&gt;, &lt;strong&gt;before_script&lt;/strong&gt;, &lt;strong&gt;script&lt;/strong&gt;, &lt;strong&gt;before_deploy&lt;/strong&gt; and &lt;strong&gt;deploy&lt;/strong&gt;. Commands will run in the Node.js 10 build environment, which pre-installs the language runtime and package manager.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node_js&lt;/span&gt;
&lt;span class="na"&gt;node_js&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  install
&lt;/h4&gt;

&lt;p&gt;In the &lt;code&gt;install&lt;/code&gt; &lt;a href="https://github.com/jthomas/openwhisk-release-verification/blob/master/.travis.yml#L5-L9"&gt;phase&lt;/a&gt;, I need to set up the build environment to deploy the application and run tests. &lt;/p&gt;

&lt;p&gt;This means installing the IBM Cloud CLI, &lt;a href="https://cloud.ibm.com/openwhisk/learn/cli"&gt;Cloud Functions CLI plugin&lt;/a&gt;, The Serverless Framework (with Apache OpenWhisk plugin), application test framework (AvaJS) and other project dependencies.&lt;/p&gt;

&lt;p&gt;The IBM Cloud CLI is installed using a shell script. Running a CLI sub-command installs the &lt;a href="https://cloud.ibm.com/openwhisk/learn/cli"&gt;Cloud Functions plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Serverless Framework is installed as global NPM package (using &lt;code&gt;npm -g install&lt;/code&gt;). The Apache OpenWhisk provider plugin is handled as &lt;a href="https://github.com/jthomas/openwhisk-release-verification/blob/master/package.json#L13-L23"&gt;normal project dependency&lt;/a&gt;, along with the test framework. Both those dependencies are installed using NPM.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl -fsSL https://clis.cloud.ibm.com/install/linux | sh&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ibmcloud plugin install cloud-functions&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm install serverless -g&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  before_script
&lt;/h4&gt;

&lt;p&gt;This &lt;a href="https://github.com/jthomas/openwhisk-release-verification/blob/master/.travis.yml#L11-L16"&gt;phase&lt;/a&gt; is used to run unit tests, catching errors in core business logic, before setting up credentials (used in the &lt;code&gt;script&lt;/code&gt; phase) for the acceptance test environment. Unit test failures will halt the build immediately, skipping test and production deployments.&lt;/p&gt;

&lt;p&gt;Custom variables provide the API key, platform endpoint, organisation and space identifiers which are used for the test environment. The CLI is authenticated using these values, before running the &lt;code&gt;ibmcloud fn api list&lt;/code&gt; command. This ensures Cloud Functions credentials are available locally, as used by The Serverless Framework.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ibmcloud login --apikey $IBMCLOUD_API_KEY -a $IBMCLOUD_API_ENDPOINT&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ibmcloud target -o $IBMCLOUD_ORG -s $IBMCLOUD_TEST_SPACE&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ibmcloud fn api list &amp;gt; /dev/null&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ibmcloud target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  script
&lt;/h4&gt;

&lt;p&gt;With the build system configured, the application can be deployed to test environment, followed by running acceptance tests. If either deployment or acceptance tests fail, the build will stop, skipping the production deployment.&lt;/p&gt;

&lt;p&gt;Acceptance tests use an environment variable to configure the hostname test cases are executed against. The &lt;code&gt;npm run acceptance-test&lt;/code&gt; alias command sets this value to the test environment hostname (&lt;code&gt;apache-api-test.jamesthom.as&lt;/code&gt;) before running the test suite.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sls deploy&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm run acceptance-test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  before_deploy
&lt;/h4&gt;

&lt;p&gt;Before deploying to production, Cloud Functions credentials need to be updated. The IBM Cloud CLI is used to target the production environment, before running a Cloud Functions CLI command. This updates local credentials with the production environment credentials.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;before_deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ibmcloud target -s $IBMCLOUD_PROD_SPACE&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ibmcloud fn api list &amp;gt; /dev/null&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ibmcloud target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  deploy
&lt;/h4&gt;

&lt;p&gt;If all the proceeding stages have successfully finished, the application can be deployed to the production. Following this final deployment, smoke tests are used to check production APIs still work as expected.&lt;/p&gt;

&lt;p&gt;Smoke tests are just the same acceptance tests executed against the production environment. The &lt;code&gt;npm run acceptance-prod&lt;/code&gt; alias command sets the hostname configuration value to the production environment  (&lt;code&gt;apache-api.jamesthom.as&lt;/code&gt;) before running the test suite.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;script&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sls deploy &amp;amp;&amp;amp; npm run acceptance-prod&lt;/span&gt;
  &lt;span class="na"&gt;skip_cleanup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Using the &lt;code&gt;skip_cleanup&lt;/code&gt; parameter leaves installed artifacts from previous phases in the build environment. This means we don't have to re-install the IBM Cloud CLI, The Serverless Framework or NPM dependencies needed to run the production deployment and smoke tests.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  success?
&lt;/h3&gt;

&lt;p&gt;If all of the &lt;a href="https://travis-ci.org/jthomas/openwhisk-release-verification"&gt;build phases&lt;/a&gt; are successful, the latest project code should have been deployed to the production environment. 💯💯💯&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9A9ifHpN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://jamesthom.as/images/build-screenshot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9A9ifHpN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://jamesthom.as/images/build-screenshot.png" alt="Build Screenshoot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the build failed due to unit test failures, the test suite can be ran locally to fix any errors. Deployment failures can be investigated using the console output logs from Travis CI. Acceptance test issues, against test or production environments, can be debugged by logging into those environments locally and running the test suite from my development machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Using Travis CI with The Serverless Framework and a JavaScript testing framework, I was able to set up a fully-automated CI/CD deployment pipeline for the Apache OpenWhisk release candidate verification tool.&lt;/p&gt;

&lt;p&gt;Using a CI/CD pipeline, rather than a manual approach, for deployments has the following advantages...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No more manual and error-prone deploys relying on a human 👨‍💻 :)&lt;/li&gt;
&lt;li&gt;Automatic unit &amp;amp; acceptance test execution catch errors before deployments.&lt;/li&gt;
&lt;li&gt;Production environment only accessed by CI/CD system, reducing accidental breakages.&lt;/li&gt;
&lt;li&gt;All cloud resources must be configured in code. No "&lt;a href="https://martinfowler.com/bliki/SnowflakeServer.html"&gt;snowflake&lt;/a&gt;" environments allowed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having finished code for new project features or bug fixes, all I have to do is push changes to the GitHub repository. This fires the Travis CI build pipeline which will automatically deploy the updated application to the production environment. If there are any issues, due to failed tests or deployments, I'll be notified by email.&lt;/p&gt;

&lt;p&gt;This allows me to get back to adding new features to the tool (and fixing bugs) rather than wrestling with deployments, managing credentials for multiple environments and then trying to remember to run tests against the correct instances!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>cicd</category>
      <category>deployments</category>
      <category>node</category>
    </item>
    <item>
      <title>Serverless Machine Learning With TensorFlow.js</title>
      <dc:creator>James Thomas</dc:creator>
      <pubDate>Mon, 13 Aug 2018 16:42:47 +0000</pubDate>
      <link>https://dev.to/jthomas/serverless-machine-learning-with-tensorflowjs-36eb</link>
      <guid>https://dev.to/jthomas/serverless-machine-learning-with-tensorflowjs-36eb</guid>
      <description>

&lt;p&gt;In a &lt;a href="http://jamesthom.as/blog/2018/08/07/machine-learning-in-node-dot-js-with-tensorflow-dot-js/"&gt;previous blog post&lt;/a&gt;, I showed how to use &lt;a href="https://js.tensorflow.org/"&gt;TensorFlow.js&lt;/a&gt; on Node.js to run &lt;a href="https://gist.github.com/jthomas/145610bdeda2638d94fab9a397eb1f1d#file-script-js"&gt;visual recognition on images from the local filesystem&lt;/a&gt;. TensorFlow.js is a JavaScript version of the open-source machine learning library from Google. &lt;/p&gt;

&lt;p&gt;Once I had this working with a local Node.js script, my next idea was to convert it into a serverless function. Running this function on &lt;a href="https://console.bluemix.net/openwhisk/"&gt;IBM Cloud Functions&lt;/a&gt; (&lt;a href="https://openwhisk.incubator.apache.org/"&gt;Apache OpenWhisk&lt;/a&gt;) would turn the script into my own visual recognition microservice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YRF-ImjK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/jrugj4ot0kofa6drt609.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YRF-ImjK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/jrugj4ot0kofa6drt609.gif" alt="Serverless TensorFlow.js Function"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sounds easy, right? It's just a JavaScript library? So, zip it up and away we go... &lt;strong&gt;&lt;em&gt;ahem&lt;/em&gt;&lt;/strong&gt; 👊&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Converting the image classification script to run in a serverless environment had the following challenges...&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TensorFlow.js libraries need to be available in the runtime.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Native bindings for the library must be compiled against the platform architecture.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Models files need to be loaded from the filesystem.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of these issues were more challenging than others to fix! Let's start by looking at the details of each issue, before explaining how &lt;a href="http://jamesthom.as/blog/2017/01/16/openwhisk-docker-actions/"&gt;Docker support&lt;/a&gt; in Apache OpenWhisk can be used to resolve them all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;h3&gt;
  
  
  TensorFlow.js Libraries
&lt;/h3&gt;

&lt;p&gt;TensorFlow.js libraries are not included in the &lt;a href="https://github.com/apache/incubator-openwhisk-runtime-nodejs"&gt;Node.js runtimes&lt;/a&gt; provided by the Apache OpenWhisk. &lt;/p&gt;

&lt;p&gt;External libraries &lt;a href="http://jamesthom.as/blog/2016/11/28/npm-modules-in-openwhisk/"&gt;can be imported&lt;/a&gt; into the runtime by deploying applications from a zip file. Custom &lt;code&gt;node_modules&lt;/code&gt; folders included in the zip file will be extracted in the runtime. Zip files are limited to a &lt;a href="https://github.com/apache/incubator-openwhisk/blob/master/docs/reference.md#actions"&gt;maximum size of 48MB&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Library Size
&lt;/h4&gt;

&lt;p&gt;Running &lt;code&gt;npm install&lt;/code&gt; for the TensorFlow.js libraries used revealed the first problem... the resulting &lt;code&gt;node_modules&lt;/code&gt; directory was 175MB. 😱&lt;/p&gt;

&lt;p&gt;Looking at the contents of this folder, the &lt;code&gt;tfjs-node&lt;/code&gt; module compiles a &lt;a href="https://github.com/tensorflow/tfjs-node/tree/master/src"&gt;native shared library&lt;/a&gt; (&lt;code&gt;libtensorflow.so&lt;/code&gt;) that is 135M. This means no amount of JavaScript minification is going to get those external dependencies under the magic 48 MB limit. 👎&lt;/p&gt;

&lt;h4&gt;
  
  
  Native Dependencies
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;libtensorflow.so&lt;/code&gt; native shared library must be compiled using the platform runtime. Running &lt;code&gt;npm install&lt;/code&gt;  locally automatically compiles native dependencies against the host platform. Local environments may use different CPU architectures (Mac vs Linux) or link against shared libraries not available in the serverless runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  MobileNet Model Files
&lt;/h3&gt;

&lt;p&gt;TensorFlow models files &lt;a href="https://js.tensorflow.org/tutorials/model-save-load.html"&gt;need loading from the filesystem&lt;/a&gt; in Node.js. Serverless runtimes do provide a temporary filesystem inside the runtime environment. Files from deployment zip files are automatically extracted into this environment before invocations. There is no external access to this filesystem outside the lifecycle of the serverless function.&lt;/p&gt;

&lt;p&gt;Models files for the MobileNet model were 16MB. If these files are included in the deployment package, it leaves 32MB for the rest of the application source code. Although the model files are small enough to include in the zip file, what about the TensorFlow.js libraries? Is this the end of the blog post? Not so fast....&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apache OpenWhisk's support for custom runtimes provides a simple solution to all these issues!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Runtimes
&lt;/h2&gt;

&lt;p&gt;Apache OpenWhisk uses Docker containers as the runtime environments for serverless functions (actions). All platform runtime images are &lt;a href="https://hub.docker.com/r/openwhisk/"&gt;published on Docker Hub&lt;/a&gt;, allowing developers to start these environments locally.&lt;/p&gt;

&lt;p&gt;Developers can also &lt;a href="https://github.com/apache/incubator-openwhisk/blob/master/docs/actions-docker.md"&gt;specify custom runtime images&lt;/a&gt; when creating actions. These images must be publicly available on Docker Hub. Custom runtimes have to expose the &lt;a href="https://github.com/apache/incubator-openwhisk/blob/master/docs/actions-new.md#action-interface"&gt;same HTTP API&lt;/a&gt; used by the platform for invoking actions.&lt;/p&gt;

&lt;p&gt;Using platform runtime images as &lt;a href="https://docs.docker.com/glossary/?term=parent%20image"&gt;parent images&lt;/a&gt; makes it simple to build custom runtimes. Users can run commands during the Docker build to install additional libraries and other dependencies. The parent image already contains source files with the HTTP API service handling platform requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  TensorFlow.js Runtime
&lt;/h3&gt;

&lt;p&gt;Here is the Docker build file for the Node.js action runtime with additional TensorFlow.js dependencies.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM openwhisk/action-nodejs-v8:latest

RUN npm install @tensorflow/tfjs @tensorflow-models/mobilenet @tensorflow/tfjs-node jpeg-js

COPY mobilenet mobilenet
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;openwhisk/action-nodejs-v8:latest&lt;/code&gt; is the Node.js action runtime image &lt;a href="https://hub.docker.com/r/openwhisk/action-nodejs-v8/"&gt;published by OpenWhisk&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;TensorFlow libraries and other dependencies are installed using &lt;code&gt;npm install&lt;/code&gt; in the build process. Native dependencies for the &lt;code&gt;@tensorflow/tfjs-node&lt;/code&gt; library are automatically compiled for the correct platform by installing during the build process.&lt;/p&gt;

&lt;p&gt;Since I'm building a new runtime, I've also added the &lt;a href="https://github.com/tensorflow/tfjs-models/tree/master/mobilenet"&gt;MobileNet model files&lt;/a&gt; to the image. Whilst not strictly necessary, removing them from the action zip file reduces deployment times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Want to skip the next step? Use this image &lt;a href="https://hub.docker.com/r/jamesthomas/action-nodejs-v8/"&gt;&lt;code&gt;jamesthomas/action-nodejs-v8:tfjs&lt;/code&gt;&lt;/a&gt; rather than building your own.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Building The Runtime
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;In the &lt;a href="http://jamesthom.as/blog/2018/08/07/machine-learning-in-node-dot-js-with-tensorflow-dot-js/"&gt;previous blog post&lt;/a&gt;, I showed how to download model files from the public storage bucket.&lt;/em&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download a version of the MobileNet model and place all files in the &lt;code&gt;mobilenet&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;Copy the Docker build file from above to a local file named &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run the Docker &lt;a href="https://docs.docker.com/engine/reference/commandline/build/"&gt;build command&lt;/a&gt; to generate a local image.&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; tfjs &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.docker.com/engine/reference/commandline/tag/"&gt;Tag the local image&lt;/a&gt; with a remote username and repository.&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;docker tag tfjs &amp;lt;USERNAME&amp;gt;/action-nodejs-v8:tfjs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Replace &lt;code&gt;&amp;lt;USERNAME&amp;gt;&lt;/code&gt; with your Docker Hub username.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.docker.com/engine/reference/commandline/push/"&gt;Push the local image&lt;/a&gt; to Docker Hub&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt; docker push &amp;lt;USERNAME&amp;gt;/action-nodejs-v8:tfjs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once the image &lt;a href="https://hub.docker.com/r/jamesthomas/action-nodejs-v8/"&gt;is available&lt;/a&gt; on Docker Hub, actions can be created using that runtime image. 😎&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Code
&lt;/h2&gt;

&lt;p&gt;This source code implements image classification as an OpenWhisk action. Image files are provided as a Base64 encoded string using the &lt;code&gt;image&lt;/code&gt; property on the event parameters. Classification results are returned as the &lt;code&gt;results&lt;/code&gt; property in the response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caching Loaded Models
&lt;/h3&gt;

&lt;p&gt;Serverless platforms initialise runtime environments on-demand to handle invocations. Once a runtime environment has been created, it will be &lt;a href="https://medium.com/openwhisk/squeezing-the-milliseconds-how-to-make-serverless-platforms-blazing-fast-aea0e9951bd0"&gt;re-used for further invocations&lt;/a&gt; with some limits. This improves performance by removing the initialisation delay ("cold start") from request processing.&lt;/p&gt;

&lt;p&gt;Applications can exploit this behaviour by using global variables to maintain state across requests. This is often use to &lt;a href="https://blog.rowanudell.com/database-connections-in-lambda/"&gt;cache opened database connections&lt;/a&gt; or store initialisation data loaded from external systems.&lt;/p&gt;

&lt;p&gt;I have used this pattern to &lt;a href="https://gist.github.com/jthomas/e7c78bbfe4091ed6ace93d1b53cbf6e5#file-index-js-L80-L82"&gt;cache the MobileNet model&lt;/a&gt; used for classification. During cold invocations, the model is loaded from the filesystem and stored in a global variable. Warm invocations then use the existence of that global variable to skip the model loading process with further requests.&lt;/p&gt;

&lt;p&gt;Caching the model reduces the time (and therefore cost) for classifications on warm invocations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory Leak
&lt;/h3&gt;

&lt;p&gt;Running the Node.js script from blog post on IBM Cloud Functions was possible with minimal modifications. Unfortunately, performance testing revealed a memory leak in the handler function. 😢&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Reading more about &lt;a href="https://js.tensorflow.org/tutorials/core-concepts.html"&gt;how TensorFlow.js works&lt;/a&gt; on Node.js uncovered the issue...&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;TensorFlow.js's Node.js extensions use a native C++ library to execute the Tensors on a CPU or GPU engine. Memory allocated for Tensor objects in the native library is retained until the application explicitly releases it or the process exits. TensorFlow.js provides a &lt;code&gt;dispose&lt;/code&gt; method on the individual objects to free allocated memory. There is also a &lt;code&gt;tf.tidy&lt;/code&gt; method to automatically clean up all allocated objects within a frame.&lt;/p&gt;

&lt;p&gt;Reviewing the code, tensors were being created as &lt;a href="https://gist.github.com/jthomas/e7c78bbfe4091ed6ace93d1b53cbf6e5#file-index-js-L51-L59"&gt;model input from images&lt;/a&gt; on each request. These objects were not disposed before returning from the request handler. This meant native memory grew unbounded. Adding an explicit &lt;code&gt;dispose&lt;/code&gt; call to free these objects before returning &lt;a href="https://gist.github.com/jthomas/e7c78bbfe4091ed6ace93d1b53cbf6e5#file-index-js-L91"&gt;fixed the issue&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Profiling &amp;amp; Performance
&lt;/h3&gt;

&lt;p&gt;Action code records memory usage and elapsed time at different stages in classification process.&lt;/p&gt;

&lt;p&gt;Recording &lt;a href="https://gist.github.com/jthomas/e7c78bbfe4091ed6ace93d1b53cbf6e5#file-index-js-L12-L20"&gt;memory usage&lt;/a&gt; allows me to modify the maximum memory allocated to the function for optimal performance and cost. Node.js provides a &lt;a href="https://nodejs.org/docs/v0.4.11/api/all.html#process.memoryUsage"&gt;standard library API&lt;/a&gt; to retrieve memory usage for the current process. Logging these values allows me to inspect memory usage at different stages.&lt;/p&gt;

&lt;p&gt;Timing &lt;a href="https://gist.github.com/jthomas/e7c78bbfe4091ed6ace93d1b53cbf6e5#file-index-js-L71"&gt;different tasks&lt;/a&gt; in the classification process, i.e. model loading, image classification, gives me an insight into how efficient classification is compared to other methods. Node.js has a &lt;a href="https://nodejs.org/api/console.html#console_console_time_label"&gt;standard library API&lt;/a&gt; for timers to record and print elapsed time to the console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Deploy Action
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Run the following command with the &lt;a href="https://console.bluemix.net/openwhisk/learn/cli"&gt;IBM Cloud CLI&lt;/a&gt; to create the action.&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ibmcloud fn action create classify &lt;span class="nt"&gt;--docker&lt;/span&gt; &amp;lt;IMAGE_NAME&amp;gt; index.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Replace &lt;code&gt;&amp;lt;IMAGE_NAME&amp;gt;&lt;/code&gt; with the public Docker Hub image identifier for the custom runtime. Use &lt;code&gt;jamesthomas/action-nodejs-v8:tfjs&lt;/code&gt; if you haven't built this manually.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing It Out
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Download &lt;a href="https://upload.wikimedia.org/wikipedia/commons/f/fe/Giant_Panda_in_Beijing_Zoo_1.JPG"&gt;this image&lt;/a&gt; of a Panda from Wikipedia.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--J9LVA8k8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/su9xscczsrjvwgcjlpyx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--J9LVA8k8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/su9xscczsrjvwgcjlpyx.jpg" alt="Panda"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;wget http://bit.ly/2JYSal9 &lt;span class="nt"&gt;-O&lt;/span&gt; panda.jpg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Invoke the action with the Base64 encoded image as an input parameter.&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt; ibmcloud fn action invoke classify &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; image &lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;base64 &lt;/span&gt;panda.jpg&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Returned JSON message contains classification probabilities. 🐼🐼🐼&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"results"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'giant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;panda&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;panda&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;panda&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;bear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;coon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;bear'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;probability&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.9993536472320557&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Activation Details
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Retrieve logging output for the last activation to show performance data.&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ibmcloud fn activation logs &lt;span class="nt"&gt;--last&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Profiling and memory usage details are logged to stdout&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;prediction &lt;span class="k"&gt;function &lt;/span&gt;called.
memory used: &lt;span class="nv"&gt;rss&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;150.46 MB, &lt;span class="nv"&gt;heapTotal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;32.83 MB, &lt;span class="nv"&gt;heapUsed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20.29 MB, &lt;span class="nv"&gt;external&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;67.6 MB
loading image and model...
decodeImage: 74.233ms
memory used: &lt;span class="nv"&gt;rss&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;141.8 MB, &lt;span class="nv"&gt;heapTotal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;24.33 MB, &lt;span class="nv"&gt;heapUsed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;19.05 MB, &lt;span class="nv"&gt;external&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;40.63 MB
imageByteArray: 5.676ms
memory used: &lt;span class="nv"&gt;rss&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;141.8 MB, &lt;span class="nv"&gt;heapTotal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;24.33 MB, &lt;span class="nv"&gt;heapUsed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;19.05 MB, &lt;span class="nv"&gt;external&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;45.51 MB
imageToInput: 5.952ms
memory used: &lt;span class="nv"&gt;rss&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;141.8 MB, &lt;span class="nv"&gt;heapTotal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;24.33 MB, &lt;span class="nv"&gt;heapUsed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;19.06 MB, &lt;span class="nv"&gt;external&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;45.51 MB
mn_model.classify: 274.805ms
memory used: &lt;span class="nv"&gt;rss&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;149.83 MB, &lt;span class="nv"&gt;heapTotal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;24.33 MB, &lt;span class="nv"&gt;heapUsed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20.57 MB, &lt;span class="nv"&gt;external&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;45.51 MB
classification results: &lt;span class="o"&gt;[&lt;/span&gt;...]
main: 356.639ms
memory used: &lt;span class="nv"&gt;rss&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;144.37 MB, &lt;span class="nv"&gt;heapTotal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;24.33 MB, &lt;span class="nv"&gt;heapUsed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20.58 MB, &lt;span class="nv"&gt;external&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;45.51 MB
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;main&lt;/code&gt; is the total elapsed time for the action handler. &lt;code&gt;mn_model.classify&lt;/code&gt; is the elapsed time for the image classification. Cold start requests print an extra log message with model loading time, &lt;code&gt;loadModel: 394.547ms&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Results
&lt;/h2&gt;

&lt;p&gt;Invoking the &lt;code&gt;classify&lt;/code&gt; action 1000 times for both cold and warm activations (using 256MB memory) generated the following performance results.&lt;/p&gt;

&lt;h3&gt;
  
  
  warm invocations
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7RTJoDm9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/4i7ii902of646eww8vrq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7RTJoDm9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/4i7ii902of646eww8vrq.png" alt="Warm Activation Performance Results"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Classifications took an average of &lt;strong&gt;316 milliseconds to process when using warm environments&lt;/strong&gt;. Looking at the timing data, converting the Base64 encoded JPEG into the input tensor took around 100 milliseconds. Running the model classification task was in the 200 - 250 milliseconds range.&lt;/p&gt;

&lt;h3&gt;
  
  
  cold invocations
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O17w9TOH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/23h2f07sm3pm5fvq887r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O17w9TOH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/23h2f07sm3pm5fvq887r.png" alt="Cold Activation Performance Results"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Classifications took an average of &lt;strong&gt;1260 milliseconds to process when using cold environments&lt;/strong&gt;. These requests incur penalties for initialising new runtime containers and loading models from the filesystem. Both of these tasks took around 400 milliseconds each.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One disadvantage of using custom runtime images in Apache OpenWhisk is the lack of &lt;a href="https://medium.com/openwhisk/squeezing-the-milliseconds-how-to-make-serverless-platforms-blazing-fast-aea0e9951bd0"&gt;pre-warmed containers&lt;/a&gt;. Pre-warming is used to reduce cold start times by starting runtime containers before they are needed. This is not supported for non-standard runtime images.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  classification cost
&lt;/h3&gt;

&lt;p&gt;IBM Cloud Functions &lt;a href="https://console.bluemix.net/openwhisk/learn/pricing"&gt;provides a free tier&lt;/a&gt; of 400,000 GB/s per month. Each further second of execution is charged at $0.000017 per GB of memory allocated. Execution time is rounded up to the nearest 100ms.&lt;/p&gt;

&lt;p&gt;If all activations were warm, a user could execute &lt;strong&gt;more than 4,000,000 classifications per month in the free tier&lt;/strong&gt; using an action with 256MB. Once outside the free tier, around 600,000 further invocations would cost just over $1.&lt;/p&gt;

&lt;p&gt;If all activations were cold, a user could execute &lt;strong&gt;more than 1,2000,000 classifications per month in the free tier&lt;/strong&gt; using an action with 256MB. Once outside the free tier, around 180,000 further invocations would cost just over $1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;TensorFlow.js brings the power of deep learning to JavaScript developers. Using pre-trained models with the TensorFlow.js library makes it simple to extend JavaScript applications with complex machine learning tasks with minimal effort and code.&lt;/p&gt;

&lt;p&gt;Getting a local script to run image classification was relatively simple, but converting to a serverless function came with more challenges! Apache OpenWhisk restricts the maximum application size to 50MB and native libraries dependencies were much larger than this limit.&lt;/p&gt;

&lt;p&gt;Fortunately, Apache OpenWhisk's custom runtime support allowed us to resolve all these issues. By building a custom runtime with native dependencies and models files, those libraries can be used on the platform without including them in the deployment package.&lt;/p&gt;


</description>
      <category>serverless</category>
      <category>machinelearning</category>
      <category>tensorflow</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Machine Learning In Node.js With TensorFlow.js</title>
      <dc:creator>James Thomas</dc:creator>
      <pubDate>Thu, 09 Aug 2018 16:41:32 +0000</pubDate>
      <link>https://dev.to/ibmdeveloper/machine-learning-in-nodejs-with-tensorflowjs-1g1p</link>
      <guid>https://dev.to/ibmdeveloper/machine-learning-in-nodejs-with-tensorflowjs-1g1p</guid>
      <description>&lt;p&gt;&lt;a href="https://js.tensorflow.org/" rel="noopener noreferrer"&gt;TensorFlow.js&lt;/a&gt; is a new version of the popular open-source library which brings deep learning to JavaScript. Developers can now define, train, and run machine learning models using the &lt;a href="https://js.tensorflow.org/api/0.12.0/" rel="noopener noreferrer"&gt;high-level library API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tensorflow/tfjs-models/" rel="noopener noreferrer"&gt;Pre-trained models&lt;/a&gt; mean developers can now easily perform complex tasks like &lt;a href="https://emojiscavengerhunt.withgoogle.com/" rel="noopener noreferrer"&gt;visual recognition&lt;/a&gt;, &lt;a href="https://magenta.tensorflow.org/demos/performance_rnn/index.html#2|2,0,1,0,1,1,0,1,0,1,0,1|1,1,1,1,1,1,1,1,1,1,1,1|1,1,1,1,1,1,1,1,1,1,1,1|false" rel="noopener noreferrer"&gt;generating music&lt;/a&gt; or &lt;a href="https://storage.googleapis.com/tfjs-models/demos/posenet/camera.html" rel="noopener noreferrer"&gt;detecting human poses&lt;/a&gt; with just a few lines of JavaScript.&lt;/p&gt;

&lt;p&gt;Having started as a front-end library for web browsers, recent updates added &lt;a href="https://github.com/tensorflow/tfjs-node" rel="noopener noreferrer"&gt;experimental support&lt;/a&gt; for Node.js. This allows TensorFlow.js to be used in backend JavaScript applications without having to use Python.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Reading about the library, I wanted to test it out with a simple task...&lt;/em&gt; 🧐&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Use TensorFlow.js to perform visual recognition on images using JavaScript from Node.js&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unfortunately, most of the &lt;a href="https://js.tensorflow.org/#getting-started" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; and &lt;a href="https://js.tensorflow.org/tutorials/webcam-transfer-learning.html" rel="noopener noreferrer"&gt;example code&lt;/a&gt; provided uses the library in a browser. &lt;a href="https://github.com/tensorflow/tfjs-models/tree/master/mobilenet" rel="noopener noreferrer"&gt;Project utilities&lt;/a&gt; provided to simplify loading and using pre-trained models have not yet been extended with Node.js support. Getting this working did end up with me spending a lot of time reading the Typescript source files for the library. 👎&lt;/p&gt;

&lt;p&gt;However, after a few days' hacking, I managed to get &lt;a href="https://gist.github.com/jthomas/145610bdeda2638d94fab9a397eb1f1d" rel="noopener noreferrer"&gt;this completed&lt;/a&gt;! Hurrah! 🤩&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Before we dive into the code, let's start with an overview of the different TensorFlow libraries.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TensorFlow
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.tensorflow.org/" rel="noopener noreferrer"&gt;TensorFlow&lt;/a&gt; is an open-source software library for machine learning applications. TensorFlow can be used to implement neural networks and other deep learning algorithms.&lt;/p&gt;

&lt;p&gt;Released by Google in November 2015, TensorFlow was originally a &lt;a href="https://www.tensorflow.org/api_docs/python/" rel="noopener noreferrer"&gt;Python library&lt;/a&gt;. It used either CPU or GPU-based computation for training and evaluating machine learning models. The library was initially designed to run on high-performance servers with expensive GPUs. &lt;/p&gt;

&lt;p&gt;Recent updates have extended the software to run in resource-constrained environments like mobile devices and web browsers.&lt;/p&gt;

&lt;h3&gt;
  
  
  TensorFlow Lite
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.tensorflow.org/mobile/tflite/" rel="noopener noreferrer"&gt;Tensorflow Lite&lt;/a&gt;, a lightweight version of the library for mobile and embedded devices, was released in May 2017. This was accompanied by a new series of pre-trained deep learning models for vision recognition tasks, called &lt;a href="https://ai.googleblog.com/2017/06/mobilenets-open-source-models-for.html" rel="noopener noreferrer"&gt;MobileNet&lt;/a&gt;. MobileNet models were designed to work efficiently in resource-constrained environments like mobile devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  TensorFlow.js
&lt;/h3&gt;

&lt;p&gt;Following Tensorflow Lite, &lt;a href="https://medium.com/tensorflow/introducing-tensorflow-js-machine-learning-in-javascript-bf3eab376db" rel="noopener noreferrer"&gt;TensorFlow.js&lt;/a&gt; was announced in March 2018. This version of the library was designed to run in the browser, building on an earlier project called &lt;a href="https://twitter.com/deeplearnjs" rel="noopener noreferrer"&gt;deeplearn.js&lt;/a&gt;. WebGL provides GPU access to the library. Developers use a JavaScript API to train, load and run models.&lt;/p&gt;

&lt;p&gt;TensorFlow.js was recently extended to run on Node.js, using an &lt;a href="https://github.com/tensorflow/tfjs-node" rel="noopener noreferrer"&gt;extension library&lt;/a&gt; called &lt;code&gt;tfjs-node&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Node.js extension is an alpha release and still under active development.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Importing Existing Models Into TensorFlow.js
&lt;/h4&gt;

&lt;p&gt;Existing TensorFlow and Keras models can be executed using the TensorFlow.js library. Models need converting to a new format &lt;a href="https://github.com/tensorflow/tfjs-converter" rel="noopener noreferrer"&gt;using this tool&lt;/a&gt; before execution. Pre-trained and converted models for image classification, pose detection and k-nearest neighbours are &lt;a href="https://github.com/tensorflow/tfjs-models" rel="noopener noreferrer"&gt;available on Github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using TensorFlow.js in Node.js
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Installing TensorFlow Libraries
&lt;/h3&gt;

&lt;p&gt;TensorFlow.js can be installed from the &lt;a href="https://www.npmjs.com/" rel="noopener noreferrer"&gt;NPM registry&lt;/a&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@tensorflow/tfjs&lt;/code&gt; - &lt;a href="https://www.npmjs.com/package/@tensorflow/tfjs" rel="noopener noreferrer"&gt;Core TensorFlow.js library&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@tensorflow/tfjs-node&lt;/code&gt; - &lt;a href="https://www.npmjs.com/package/@tensorflow/tfjs-node" rel="noopener noreferrer"&gt;TensorFlow.js Node.js extension&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@tensorflow/tfjs-node-gpu&lt;/code&gt; - &lt;a href="https://www.npmjs.com/package/@tensorflow/tfjs-node-gpu" rel="noopener noreferrer"&gt;TensorFlow.js Node.js extension with GPU support&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @tensorflow/tfjs @tensorflow/tfjs-node
// or...
npm install @tensorflow/tfjs @tensorflow/tfjs-node-gpu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both Node.js extensions use native dependencies which will be compiled on demand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Loading TensorFlow Libraries
&lt;/h3&gt;

&lt;p&gt;TensorFlow's &lt;a href="https://js.tensorflow.org/api/0.12.0/" rel="noopener noreferrer"&gt;JavaScript API&lt;/a&gt; is exposed from the core library. Extension modules to enable Node.js support do not expose additional APIs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tensorflow/tfjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Load the binding (CPU computation)&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tensorflow/tfjs-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Or load the binding (GPU computation)&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tensorflow/tfjs-node-gpu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Loading TensorFlow Models
&lt;/h3&gt;

&lt;p&gt;TensorFlow.js provides an &lt;a href="https://github.com/tensorflow/tfjs-models" rel="noopener noreferrer"&gt;NPM library&lt;/a&gt; (&lt;code&gt;tfjs-models&lt;/code&gt;) to ease loading pre-trained &amp;amp; converted models for &lt;a href="https://github.com/tensorflow/tfjs-models/tree/master/mobilenet" rel="noopener noreferrer"&gt;image classification&lt;/a&gt;, &lt;a href="https://github.com/tensorflow/tfjs-models/tree/master/posenet" rel="noopener noreferrer"&gt;pose detection&lt;/a&gt; and &lt;a href="https://github.com/tensorflow/tfjs-models/tree/master/knn-classifier" rel="noopener noreferrer"&gt;k-nearest neighbours&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/tensorflow/tfjs-models/tree/master/mobilenet" rel="noopener noreferrer"&gt;MobileNet model&lt;/a&gt; used for image classification is a deep neural network trained to &lt;a href="https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/imagenet_classes.ts" rel="noopener noreferrer"&gt;identify 1000 different classes&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;In the project's README, the &lt;a href="https://github.com/tensorflow/tfjs-models/tree/master/mobilenet#via-npm" rel="noopener noreferrer"&gt;following example code&lt;/a&gt; is used to load the model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;mobilenet&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tensorflow-models/mobilenet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Load the model.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mobilenet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;One of the first challenges I encountered was that this does not work on Node.js.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: browserHTTPRequest is not supported outside the web browser.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking at the &lt;a href="https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/index.ts#L27" rel="noopener noreferrer"&gt;source code&lt;/a&gt;, the &lt;code&gt;mobilenet&lt;/code&gt; library is a wrapper around the underlying &lt;code&gt;tf.Model&lt;/code&gt; class. When the &lt;code&gt;load()&lt;/code&gt; method is called, it automatically downloads the correct model files from an external HTTP address and instantiates the TensorFlow model. &lt;/p&gt;

&lt;p&gt;The Node.js extension does not yet support HTTP requests to dynamically retrieve models. Instead, models must be manually loaded from the filesystem.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;After reading the source code for the library, I managed to create a work-around...&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Loading Models From a Filesystem
&lt;/h4&gt;

&lt;p&gt;Rather than calling the module's &lt;code&gt;load&lt;/code&gt; method, if the &lt;code&gt;MobileNet&lt;/code&gt; class is created manually, the auto-generated &lt;code&gt;path&lt;/code&gt; variable which contains the HTTP address of the model can be overwritten with a local filesystem path. Having done this, calling the &lt;code&gt;load&lt;/code&gt; method on the class instance will trigger the &lt;a href="https://js.tensorflow.org/tutorials/model-save-load.html" rel="noopener noreferrer"&gt;filesystem loader class&lt;/a&gt;, rather than trying to use the browser-based HTTP loader.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mobilenet/model.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mobilenet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MobileNet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;mn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`file://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Awesome, it works!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But how where do the models files come from?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  MobileNet Models
&lt;/h3&gt;

&lt;p&gt;Models for TensorFlow.js consist of two file types, a model configuration file stored in JSON and model weights in a binary format. Model weights are often sharded into multiple files for better caching by browsers.&lt;/p&gt;

&lt;p&gt;Looking at the &lt;a href="https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/index.ts#L68-L76" rel="noopener noreferrer"&gt;automatic loading code&lt;/a&gt; for MobileNet models, models configuration and weight shards are retrieved from a public storage bucket at this address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v${version}_${alpha}_${size}/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template parameters in the URL refer to the model versions listed &lt;a href="https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet_v1.md#pre-trained-models" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Classification accuracy results for each version are also shown on that page.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;According to the &lt;a href="https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/index.ts#L36" rel="noopener noreferrer"&gt;source code&lt;/a&gt;, only MobileNet v1 models can be loaded using the &lt;code&gt;tensorflow-models/mobilenet&lt;/code&gt; library.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The HTTP retrieval code loads the &lt;code&gt;model.json&lt;/code&gt; file from this location and then recursively fetches all referenced model weights shards. These files are in the format &lt;code&gt;groupX-shard1of1&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Downloading Models Manually
&lt;/h4&gt;

&lt;p&gt;Saving all model files to a filesystem can be achieved by retrieving the model configuration file, parsing out the referenced weight files and downloading each weight file manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I want to use the MobileNet V1 Module with 1.0 alpha value and image size of 224 pixels.&lt;/strong&gt; This gives me the &lt;a href="https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/model.json" rel="noopener noreferrer"&gt;following URL&lt;/a&gt; for the model configuration file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/model.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this file has been downloaded locally, I can use the &lt;a href="https://stedolan.github.io/jq/" rel="noopener noreferrer"&gt;&lt;code&gt;jq&lt;/code&gt; tool&lt;/a&gt; to parse all the weight file names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat model.json | jq -r ".weightsManifest[].paths[0]"
group1-shard1of1
group2-shard1of1
group3-shard1of1
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the &lt;code&gt;sed&lt;/code&gt; tool, I can prefix these names with the HTTP URL to generate URLs for each weight file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat model.json | jq -r ".weightsManifest[].paths[0]" | sed 's/^/https:\/\/storage.googleapis.com\/tfjs-models\/tfjs\/mobilenet_v1_1.0_224\//'
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/group1-shard1of1
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/group2-shard1of1
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/group3-shard1of1
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the &lt;code&gt;parallel&lt;/code&gt; and &lt;code&gt;curl&lt;/code&gt; commands, I can then download all of these files to my local directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat model.json | jq -r ".weightsManifest[].paths[0]" | sed 's/^/https:\/\/storage.googleapis.com\/tfjs-models\/tfjs\/mobilenet_v1_1.0_224\//' |  parallel curl -O
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Classifying Images
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/tensorflow/tfjs-models/tree/master/mobilenet#via-npm" rel="noopener noreferrer"&gt;This example code&lt;/a&gt; is provided by TensorFlow.js to demonstrate returning classifications for an image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;img&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Classify the image.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;predictions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This does not work on Node.js due to the lack of a DOM.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;classify&lt;/code&gt; &lt;a href="https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/index.ts#L143-L155" rel="noopener noreferrer"&gt;method&lt;/a&gt; accepts numerous DOM elements (&lt;code&gt;canvas&lt;/code&gt;, &lt;code&gt;video&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;) and will automatically retrieve and convert image bytes from these elements into a &lt;a href="https://js.tensorflow.org/api/latest/index.html#tensor3d" rel="noopener noreferrer"&gt;&lt;code&gt;tf.Tensor3D&lt;/code&gt; class&lt;/a&gt; which is used as the input to the model. Alternatively, the &lt;code&gt;tf.Tensor3D&lt;/code&gt; input can be passed directly. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rather than trying to use an external package to simulate a DOM element in Node.js, I found it easier to construct the &lt;code&gt;tf.Tensor3D&lt;/code&gt; manually.&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Generating Tensor3D from an Image
&lt;/h4&gt;

&lt;p&gt;Reading the &lt;a href="https://github.com/tensorflow/tfjs-core/blob/master/src/kernels/backend_cpu.ts#L126-L140" rel="noopener noreferrer"&gt;source code&lt;/a&gt; for the method used to turn DOM elements into Tensor3D classes, the following input parameters are used to generate the Tensor3D class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Int32Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;numChannels&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// fill pixels with pixel channel bytes from image&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outShape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;numChannels&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tensor3d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outShape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;int32&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pixels&lt;/code&gt; is a 2D array of type (Int32Array) which contains a sequential list of channel values for each pixel. &lt;code&gt;numChannels&lt;/code&gt; is the number of channel values per pixel.&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating Input Values For JPEGs
&lt;/h4&gt;

&lt;p&gt;The &lt;a href="https://www.npmjs.com/package/jpeg-js" rel="noopener noreferrer"&gt;&lt;code&gt;jpeg-js&lt;/code&gt; library&lt;/a&gt; is a pure javascript JPEG encoder and decoder for Node.js. Using this library the RGB values for each pixel can be extracted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pixels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will return a &lt;code&gt;Uint8Array&lt;/code&gt; with four channel values (&lt;code&gt;RGBA&lt;/code&gt;) for each pixel (&lt;code&gt;width * height&lt;/code&gt;). The MobileNet model only uses the three colour channels (&lt;code&gt;RGB&lt;/code&gt;) for classification, ignoring the alpha channel. This code converts the four channel array into the correct three channel version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numChannels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numPixels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Int32Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numPixels&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;numChannels&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;numPixels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;numChannels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;numChannels&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pixels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  MobileNet Models Input Requirements
&lt;/h4&gt;

&lt;p&gt;The &lt;a href="https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet_v1.md#mobilenet_v1" rel="noopener noreferrer"&gt;MobileNet model&lt;/a&gt; being used classifies images of width and height 224 pixels. Input tensors must contain float values, between -1 and 1, for each of the three channels pixel values.&lt;/p&gt;

&lt;p&gt;Input values for images of different dimensions needs to be re-sized before classification. Additionally, pixels values from the JPEG decoder are in the range &lt;em&gt;0 - 255&lt;/em&gt;, rather than &lt;em&gt;-1 to 1&lt;/em&gt;. These values also need converting prior to classification.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TensorFlow.js has library methods to make this process easier but, fortunately for us, the &lt;code&gt;tfjs-models/mobilenet&lt;/code&gt; library &lt;a href="https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/index.ts#L103-L114" rel="noopener noreferrer"&gt;automatically handles&lt;/a&gt; this issue!&lt;/strong&gt; 👍&lt;/p&gt;

&lt;p&gt;Developers can pass in Tensor3D inputs of type &lt;code&gt;int32&lt;/code&gt;  and different dimensions to the  &lt;code&gt;classify&lt;/code&gt; method and it converts the input to the correct format prior to classification. Which means there's nothing to do... Super 🕺🕺🕺.&lt;/p&gt;

&lt;h4&gt;
  
  
  Obtaining Predictions
&lt;/h4&gt;

&lt;p&gt;MobileNet models in Tensorflow are trained to recognise entities from the &lt;a href="https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/imagenet_classes.ts" rel="noopener noreferrer"&gt;top 1000 classes&lt;/a&gt; in the &lt;a href="http://image-net.org/" rel="noopener noreferrer"&gt;ImageNet&lt;/a&gt; dataset. The models output the probabilities that each of those entities is in the image being classified.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full list of trained classes for the model being used can be found in &lt;a href="https://github.com/tensorflow/tfjs-models/blob/master/mobilenet/src/imagenet_classes.ts" rel="noopener noreferrer"&gt;this file&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;tfjs-models/mobilenet&lt;/code&gt; library exposes a &lt;code&gt;classify&lt;/code&gt; method on the &lt;code&gt;MobileNet&lt;/code&gt; class to return the top X classes with highest probabilities from an image input.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;predictions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mn_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;predictions&lt;/code&gt; is an array of X classes and probabilities in the following format.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;panda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;probability&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9993536472320557&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Having worked how to use the TensorFlow.js library and MobileNet models on Node.js, &lt;a href="https://gist.github.com/jthomas/145610bdeda2638d94fab9a397eb1f1d" rel="noopener noreferrer"&gt;this script&lt;/a&gt; will classify an image given as a command-line argument.&lt;/p&gt;

&lt;h3&gt;
  
  
  source code
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Save this script file and package descriptor to local files.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  testing it out
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Download the model files to a &lt;code&gt;mobilenet&lt;/code&gt; directory using the instructions above.&lt;/li&gt;
&lt;li&gt;Install the project dependencies using NPM
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Download a sample JPEG file to classify
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wget http://bit.ly/2JYSal9 -O panda.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2hth5o8elymeqzejtfgc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2hth5o8elymeqzejtfgc.jpg" alt="Panda"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run the script with the model file and input image as arguments.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node script.js mobilenet/model.json panda.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;If everything worked, the following output should be printed to the console.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;classification&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;giant panda, panda, panda bear, coon bear&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;probability&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9993536472320557&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The image is correctly classified as containing a Panda with 99.93% probability! 🐼🐼🐼&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;TensorFlow.js brings the power of deep learning to JavaScript developers. Using pre-trained models with the TensorFlow.js library makes it simple to extend JavaScript applications with complex machine learning tasks with minimal effort and code.&lt;/p&gt;

&lt;p&gt;Having been released as a browser-based library, TensorFlow.js has now been extended to work on Node.js, although not all of the tools and utilities support the new runtime. With a few days' hacking, I was able to use the library with the MobileNet models for visual recognition on images from a local file.&lt;/p&gt;

&lt;p&gt;Getting this working in the Node.js runtime means I now move on to my next idea... making this run inside a serverless function! Come back soon to read about my next adventure with TensorFlow.js. 👋&lt;/p&gt;

</description>
      <category>node</category>
      <category>machinelearning</category>
      <category>tensorflow</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
