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.23BRunning 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.84KBWe’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
- Economies of scale are more important than intuition would suggest for efficiently serving requests
- 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