How much load can be served from 1m² of sunlight?

4 min read Original article ↗

Timothy Downs

Let’s set up a webserver and power it only from sunlight, plus a battery for night-time. We’ll see how much load we can handle. Our testing setup is:

  • 200-watt solar panel (1.48m by 0.67m,~1 square meter)
  • 12v MPPT to convert solar panel variable voltage to battery fixed voltage
  • 12v 100ah Lithium Iron Phosphate Battery — 92% efficient
  • 12v to 240v inverter — 92% efficient
  • An AMD Ryzen 9 4900HS CPU (in a laptop with the screen turned off)
  • An electricity meter to measure how many watts of power the computer is drawing
  • A 4.6b year old yellow dwarf as a light source

Press enter or click to view image in full size

Based on my recent data, spring on the east coast of Australia gives sunshine for 3 full hours of panel capacity daily (after night-time and weather), or 600 watt-hours of energy per day. If we divide 600Wh (daily energy available) by 24 (hours in a day), we reach around 25W of constant power available without depleting the battery. Removing the efficiency losses from battery and inverter, this brings us down to a constant supply of 21W.

Our energy budget: 21W

With an idle load, this particular laptop draws 14W of power with the screen turned off.

Let’s give our laptop some arbitrary work to do:

  • Read a compressed large JSON payload (10mb) from Redis
  • Gunzip the payload, and deserialize the JSON
const express = require('express')
const Redis = require('ioredis')
const zlib = require('zlib');
const app = express()
const port = 3000
const ungzip = (input, options) => {
const promise = new Promise(function(resolve, reject) {
zlib.gunzip(input, options, function (error, result) {
if(!error) resolve(result);
else reject(Error(error));
});
});
return promise;
}
app.get('/', async (req, res) => {
const redis = new Redis(6379, "192.168.1.100") // Pull a compressed payload from a nearby Redis instance
const payload = await redis.getBuffer('payload')
const gunzipped = await ungzip(payload) // This payload gunzips to 10.5mb
let parsed = JSON.parse(gunzipped)
res.send("hello..")
})
app.listen(port, () => {
console.log(`Solar app listening at http://localhost:${port}`)
})

In order to take advantage of available cores, this Node.js application is being split across 4 cores using pm2.

Running a small load test with 1 concurrent client, we hit 3.3 RPS — requests per second, the power consumption rises from 14W idle to 26W. This gives us 26/3.3 = 7.9 watts per RPS.

tim@g14:~/git/wrk$ ./wrk -d 12 -c 1 -t 1 http://127.0.0.1:3000/
Running 12s test @ http://127.0.0.1:3000/
1 threads and 1 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 298.23ms 39.31ms 367.03ms 60.00%
Req/Sec 3.12 0.79 5.00 75.00%
40 requests in 12.02s, 9.10KB read
Requests/sec: 3.33
Transfer/sec: 775.23B

Running a larger load test with 4 concurrent clients, we hit 12.4 RPS, and we raise the power consumption from 14W idle to 42W. This gives us 42/12.4 = 3.4 watts per RPS.

tim@g14:~/git/wrk$ ./wrk -d 12 -c 4 -t 1 http://127.0.0.1:3000/
Running 12s test @ http://127.0.0.1:3000/
1 threads and 4 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 327.83ms 140.60ms 1.48s 96.08%
Req/Sec 13.73 6.61 30.00 81.82%
150 requests in 12.02s, 34.13KB read
Requests/sec: 12.48
Transfer/sec: 2.84KB

We’re spending 14 watts of our energy budget just to power the idle CPU & RAM. The additional 7 watts we have for our constant energy budget of 21W is already exceeded from one client repeating their request as it drains 26W — so to reduce to utilisation to 21W we need to make a request (300ms of processing), then 500ms pause before making the next request. This gives us 1.2 RPS and averages our consumption at 21 watts.

This answers the original question — 1 square meter of sunlight gives us a constant 21w, which delivers 1.2 request per second for an arbitrary computation. What if have 2 square meters of sunlight?

Let’s double our energy budget from 21W to 42W. By coincidence, this matches one of the above tests, a load test with 4 simultaneous users which achieves a total energy consumption of 42w — and gives us 12.48 RPS. By doubling our power consumption, we’ve gone from ~1 RPS to ~12 RPS. By increasing our utilization rate, we have increased power efficiency by a factor of 6.

Conclusions

  1. Economies of scale are more important than intuition would suggest for efficiently serving requests
  2. Low CPU utilization rate can massively reduce power efficiency due to a computers baseload power consumption

Next Steps

Please let me know in the comments below where you like to test next! Some topics I’ll cover if there is interest:

  • Discussion CPU utilization rates for these tests — my initial sense is that I’m running at ~4% CPU utilisation for 21w, 25% CPU utilisation for 42w
  • See how many RPS a Raspberry PI 4 (6 watts under load) can handle for the same task