Skip to content

Commit 8b42e44

Browse files
authored
Merge pull request #1923 from brefphp/cold-start-tracking
Track cold starts
2 parents 3249176 + a872f23 commit 8b42e44

6 files changed

Lines changed: 146 additions & 6 deletions

File tree

docs/runtimes/runtimes-details.mdx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ So far, knowing the number of Bref invocations has helped Bref grow, be recogniz
9393

9494
### Data
9595

96+
You can find the latest number of AWS Lambda invocations on the [Bref homepage](https://bref.sh/).
97+
9698
On the month of the Bref 1.0 release, [Bref was powering 1 billion invocations per month](../../news/01-bref-1.0.md#1-billion-executions-per-month).
9799

98100
On the month of the Bref 2.0 release, [Bref was powering 10 billion invocations per month](../../news/02-bref-2.0.md).
@@ -101,15 +103,15 @@ On the month of the Bref 2.0 release, [Bref was powering 10 billion invocations
101103

102104
The data sent in the ping is completely anonymous. It does not contain any identifiable data about anything (the project, users, etc.).
103105

104-
**The only data it contains is:** "A Bref invocation happened with the layer XYZ" (where XYZ is the name of the Bref layer, like "function", "fpm" or "console").
106+
**The only data it contains is:** A Bref invocation happened with the layer XYZ (where XYZ is the name of the Bref layer, like "function", "fpm" or "console") and whether the invocation was a cold start.
105107

106108
Here is an example payload:
107109

108110
```
109-
Invocations_100:1|c\nLayer_fpm_100:1|c
111+
Invocations_100:1|c\nLayer_fpm_100:1|c\nCold_100:1\nWarm_100:0|c
110112
```
111113

112-
Anyone can inspect the code and the data sent by checking the [`Bref\Runtime\LambdaRuntime::ping()` function](https://github.com/brefphp/bref/blob/master/src/Runtime/LambdaRuntime.php#L374).
114+
Anyone can inspect the code and the data sent by checking the [`Bref\Runtime\LambdaRuntime::ping()` function](https://github.com/brefphp/bref/blob/master/src/Runtime/LambdaRuntime.php#L387).
113115

114116
### How is it sent
115117

src/ConsoleRuntime/Main.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Bref\Bref;
66
use Bref\Context\Context;
77
use Bref\LazySecretsLoader;
8+
use Bref\Runtime\ColdStartTracker;
89
use Bref\Runtime\LambdaRuntime;
910
use Symfony\Component\Process\Process;
1011

@@ -15,6 +16,8 @@ class Main
1516
{
1617
public static function run(): void
1718
{
19+
ColdStartTracker::init();
20+
1821
LazySecretsLoader::loadSecretEnvironmentVariables();
1922

2023
Bref::triggerHooks('beforeStartup');
@@ -30,6 +33,8 @@ public static function run(): void
3033

3134
Bref::events()->afterStartup();
3235

36+
ColdStartTracker::coldStartFinished();
37+
3338
/** @phpstan-ignore-next-line */
3439
while (true) {
3540
$lambdaRuntime->processNextEvent(function ($event, Context $context) use ($handlerFile): array {

src/FpmRuntime/Main.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Bref\Bref;
66
use Bref\LazySecretsLoader;
7+
use Bref\Runtime\ColdStartTracker;
78
use Bref\Runtime\LambdaRuntime;
89
use RuntimeException;
910
use Throwable;
@@ -19,6 +20,8 @@ public static function run(): void
1920
ini_set('display_errors', '1');
2021
error_reporting(E_ALL);
2122

23+
ColdStartTracker::init();
24+
2225
LazySecretsLoader::loadSecretEnvironmentVariables();
2326

2427
Bref::triggerHooks('beforeStartup');
@@ -41,6 +44,8 @@ public static function run(): void
4144

4245
Bref::events()->afterStartup();
4346

47+
ColdStartTracker::coldStartFinished();
48+
4449
/** @phpstan-ignore-next-line */
4550
while (true) {
4651
$lambdaRuntime->processNextEvent($phpFpm);

src/FunctionRuntime/Main.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Bref\Bref;
66
use Bref\LazySecretsLoader;
7+
use Bref\Runtime\ColdStartTracker;
78
use Bref\Runtime\LambdaRuntime;
89
use Throwable;
910

@@ -14,6 +15,8 @@ class Main
1415
{
1516
public static function run(): void
1617
{
18+
ColdStartTracker::init();
19+
1720
LazySecretsLoader::loadSecretEnvironmentVariables();
1821

1922
Bref::triggerHooks('beforeStartup');
@@ -31,6 +34,8 @@ public static function run(): void
3134

3235
Bref::events()->afterStartup();
3336

37+
ColdStartTracker::coldStartFinished();
38+
3439
$loopMax = getenv('BREF_LOOP_MAX') ?: 1;
3540
$loops = 0;
3641
while (true) {

src/Runtime/ColdStartTracker.php

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Bref\Runtime;
4+
5+
/**
6+
* Tracks cold starts.
7+
*
8+
* @internal
9+
*/
10+
class ColdStartTracker
11+
{
12+
private const FILE = '/tmp/.bref-cold-start';
13+
14+
private static bool $currentInvocationIsColdStart = false;
15+
private static ?float $coldStartBeginningTime = null;
16+
private static ?float $coldStartEndedTime = null;
17+
private static bool $hasFirstInvocationStarted = false;
18+
private static bool $wasProactiveInitialization = false;
19+
20+
public static function init(): void
21+
{
22+
self::$coldStartBeginningTime = microtime(true);
23+
24+
// We need to use a file to track cold starts only once.
25+
// This is because Bref's process can restart between invocations,
26+
// so we can't rely on static variables to track cold starts.
27+
self::$currentInvocationIsColdStart = ! file_exists(self::FILE);
28+
if (self::$currentInvocationIsColdStart) {
29+
touch(self::FILE);
30+
}
31+
}
32+
33+
/**
34+
* Signals that the cold start has finished.
35+
*/
36+
public static function coldStartFinished(): void
37+
{
38+
self::$coldStartEndedTime = microtime(true);
39+
}
40+
41+
/**
42+
* Signals that a Lambda invocation has started.
43+
*/
44+
public static function invocationStarted(): void
45+
{
46+
// If the first invocation had happened already, then we are starting a 2nd invocation (or more)
47+
// so we are no longer in the cold start invocation anymore
48+
if (self::$hasFirstInvocationStarted) {
49+
self::$currentInvocationIsColdStart = false;
50+
return;
51+
}
52+
53+
self::$hasFirstInvocationStarted = true;
54+
55+
if (self::$currentInvocationIsColdStart) {
56+
// There was a cold start, let's figure out if it was a proactive initialization
57+
$timeElapsedSinceColdStartEnded = microtime(true) - self::$coldStartEndedTime;
58+
// If more than 100ms have passed since the cold start ended, we can assume the
59+
// Lambda sandbox was paused/frozen between the cold start and the first invocation
60+
// (100ms is an arbitrary value, we could use a lower value but I want to be conservative)
61+
// That means the Lambda sandbox was initialized proactively
62+
self::$wasProactiveInitialization = $timeElapsedSinceColdStartEnded > 0.1;
63+
} else {
64+
// There was no cold start, we are in a warm start
65+
self::$wasProactiveInitialization = false;
66+
}
67+
}
68+
69+
/**
70+
* Timestamp of the beginning of the cold start.
71+
*/
72+
public static function getColdStartBeginningTime(): float
73+
{
74+
return self::$coldStartBeginningTime;
75+
}
76+
77+
/**
78+
* Timestamp of the end of the cold start.
79+
*/
80+
public static function getColdStartEndedTime(): float
81+
{
82+
return self::$coldStartEndedTime;
83+
}
84+
85+
/**
86+
* Returns `true` if the current Lambda invocation contained a cold start.
87+
*
88+
* This is `true` even if the cold start was a proactive initialization.
89+
*
90+
* This is no longer `true` once the second invocation (and subsequent invocations) start.
91+
*/
92+
public static function currentInvocationIsColdStart(): bool
93+
{
94+
return self::$currentInvocationIsColdStart;
95+
}
96+
97+
/**
98+
* Returns `true` if the current Lambda invocation contains a cold start that was "user-facing".
99+
*
100+
* "User-facing" means that the cold start duration was part of the invocation duration that the
101+
* invoker of the Lambda function experienced.
102+
*
103+
* For example, if the application is a web application, a "user-facing" cold start of 1 second
104+
* means that the response time of the first request contained a 1 second delay.
105+
*/
106+
public static function currentInvocationIsUserFacingColdStart(): bool
107+
{
108+
return self::currentInvocationIsColdStart() && ! self::wasProactiveInitialization();
109+
}
110+
111+
/**
112+
* Returns `true` if this Lambda sandbox was initialized proactively.
113+
*/
114+
public static function wasProactiveInitialization(): bool
115+
{
116+
return self::$wasProactiveInitialization;
117+
}
118+
}

src/Runtime/LambdaRuntime.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ public function processNextEvent(Handler | RequestHandlerInterface | callable $h
8686
$this->setEnv('LAMBDA_INVOCATION_CONTEXT', json_encode($context, JSON_THROW_ON_ERROR));
8787

8888
try {
89+
ColdStartTracker::invocationStarted();
90+
8991
Bref::triggerHooks('beforeInvoke');
9092
Bref::events()->beforeInvoke($handler, $event, $context);
9193

@@ -400,20 +402,23 @@ private function ping(): void
400402
return;
401403
}
402404

405+
$isColdStart = ColdStartTracker::currentInvocationIsUserFacingColdStart() ? '1' : '0';
406+
$isWarmInvocation = $isColdStart === '0' ? '1' : '0';
407+
403408
/**
404409
* Here is the content sent to the Bref analytics server.
405-
* It signals an invocation happened on which layer.
410+
* It signals an invocation happened on which layer and whether it was a cold start.
406411
* Nothing else is sent.
407412
*
408-
* `Invocations_100` is used to signal that this is 1 ping equals 100 invocations.
413+
* `Invocations_100` is used to signal that 1 ping equals 100 invocations.
409414
* We could use statsd sample rate system like this:
410415
* `Invocations:1|c|@0.01`
411416
* but this doesn't seem to be compatible with the bridge that forwards
412417
* the metric into CloudWatch.
413418
*
414419
* See https://github.com/statsd/statsd/blob/master/docs/metric_types.md for more information.
415420
*/
416-
$message = "Invocations_100:1|c\nLayer_{$this->layer}_100:1|c";
421+
$message = "Invocations_100:1|c\nLayer_{$this->layer}_100:1|c\nCold_100:$isColdStart|c\nWarm_100:$isWarmInvocation|c";
417422

418423
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
419424
// This IP address is the Bref server.

0 commit comments

Comments
 (0)