Skip to main content

Windows version of uvloop

Project description

Code style: black

IDIM

Winloop

PyPI version PyPI - Downloads License: MIT License: Apache-2.0

An alternative library for uvloop compatibility with Windows because let's face it. Windows' Python asyncio standard library is garbage, especially when Windows Defender decides to eat half of your RAM. I never really liked the fact that I couldn't make anything run faster especially when you have fiber internet connections in place and you've done all the optimizations you could possibly think of. It always felt disappointing when libuv is available for Windows but Windows was never compatible with uvloop.

Since nobody was willing to step in after so many years of waiting, I went ahead and downloaded the source code for uvloop and started modifying the source code to be Windows compatible by carefully removing and changing parts that were not made for Windows. Many hours of research went into making this library.

The differences with uvloop is that forking has been fully disabled and some smaller API calls had to be changed, error handling has been carefully modified and subprocesses release the GIL instead of forking out...

There is a performance increase of about 5 times with Winloop compared to using the WindowsSelectorEventLoopPolicy and WindowsProactorEventLoopPolicy which have been known to trigger SSL problems in Python 3.9. Winloop is a very good replacement for solving those SSL problems as well. This library also has comparable performance to its brother uvloop.

How to install Winloop on your Windows Operating System

pip install winloop

You can also clone the repository and build the extension yourself by running the command below if you wish to use or build this library locally. Note that you will need Cython and The Visual C++ extensions to compile this library on your own.

python setup.py build_ext --inplace

Reporting issues

If you find any bugs with this library be sure to open up an issue in the issuetracker. Me and other contributors will be happy try to help you figure out and diagnose your problems.

Making pull requests

We encourage anyone to make pull-requests to Winloop, containing anything from spelling mistakes to vulnerability patches. Every little bit helps keep this library maintained and alive. Make sure that you are able to compile the library with the steps shown above. We plan to implement a nightly workflow to verify one's pull-request in the future.

try:
    import aiohttp
    import aiohttp.web
except ImportError:
    skip_tests = True
else:
    skip_tests = False

import asyncio
import unittest
import weakref
import winloop
import sys

class TestAioHTTP(unittest.TestCase):
    def __init__(self, methodName: str = "test_aiohttp_basic_1") -> None:
        super().__init__(methodName)


    def setUp(self):
        self.loop = asyncio.get_event_loop()

    def test_aiohttp_basic_1(self):
        PAYLOAD = '<h1>It Works!</h1>' * 10000

        async def on_request(request):
            return aiohttp.web.Response(text=PAYLOAD)

        asyncio.set_event_loop(self.loop)
        app = aiohttp.web.Application()
        app.router.add_get('/', on_request)

        runner = aiohttp.web.AppRunner(app)
        self.loop.run_until_complete(runner.setup())
        site = aiohttp.web.TCPSite(runner, '0.0.0.0', '10000')
        self.loop.run_until_complete(site.start())
        port = site._server.sockets[0].getsockname()[1]

        async def test():
            # Make sure we're using the correct event loop.
            self.assertIs(asyncio.get_event_loop(), self.loop)

            for addr in (('localhost', port),
                         ('127.0.0.1', port)):
                async with aiohttp.ClientSession() as client:
                    async with client.get('http://{}:{}'.format(*addr)) as r:
                        self.assertEqual(r.status, 200)
                        result = await r.text()
                        self.assertEqual(result, PAYLOAD)

        self.loop.run_until_complete(test())
        self.loop.run_until_complete(runner.cleanup())

    def test_aiohttp_graceful_shutdown(self):
        async def websocket_handler(request):
            ws = aiohttp.web.WebSocketResponse()
            await ws.prepare(request)
            request.app['websockets'].add(ws)
            try:
                async for msg in ws:
                    await ws.send_str(msg.data)
            finally:
                request.app['websockets'].discard(ws)
            return ws

        async def on_shutdown(app):
            for ws in set(app['websockets']):
                await ws.close(
                    code=aiohttp.WSCloseCode.GOING_AWAY,
                    message='Server shutdown')

        asyncio.set_event_loop(self.loop)
        app = aiohttp.web.Application()
        app.router.add_get('/', websocket_handler)
        app.on_shutdown.append(on_shutdown)
        app['websockets'] = weakref.WeakSet()

        runner = aiohttp.web.AppRunner(app)
        self.loop.run_until_complete(runner.setup())
        site = aiohttp.web.TCPSite(runner, '0.0.0.0', '10000')
        self.loop.run_until_complete(site.start())
        port = site._server.sockets[0].getsockname()[1]

        async def client():
            async with aiohttp.ClientSession() as client:
                async with client.ws_connect(
                        'http://127.0.0.1:{}'.format(port)) as ws:
                    await ws.send_str("hello")
                    async for msg in ws:
                        assert msg.data == "hello"

        client_task = asyncio.ensure_future(client())

        async def stop():
            await asyncio.sleep(0.1)
            try:
                await asyncio.wait_for(runner.cleanup(), timeout=0.1)
            except Exception as e:
                print(e)
            finally:
                try:
                    client_task.cancel()
                    await client_task
                except asyncio.CancelledError:
                    pass

        self.loop.run_until_complete(stop())



if __name__ == "__main__":
    # print("tesing without winloop")
    # asyncio.DefaultEventLoopPolicy = asyncio.WindowsSelectorEventLoopPolicy
    # asyncio.DefaultEventLoopPolicy = asyncio.WindowsProactorEventLoopPolicy
    unittest.main()
    # Looks like winloop might be 3x faster than the Proctor Event Loop , THAT's A HUGE IMPROVEMENT!
    print("testing again but with winloop enabled")
    winloop.install()
    unittest.main()

The benchmarks for the code above are as follows

Benchmarks

TCP Connections


Asyncio Event Loop Policy Time (in Seconds)
WinLoopPolicy 0.493s
WindowsProactorEventLoopPolicy 2.510s
WindowsSelectorEventLoopPolicy 2.723s

That's a massive increase and jump from just TCP alone! I'll be posting more benchmarks soon, as I modify more of the current test suites made by uvloop...

How to Use Winloop with Fastapi

This was a cool little script I put together just to make Fastapi that much faster to handle:

# TODO this code example is deprecated
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
import winloop
import uvicorn
import asyncio
import datetime

app = FastAPI()

@app.on_event("startup")
def make_assertion():
    # Check to make sure that we bypassed the original eventloop Policy....
    assert isinstance(asyncio.get_event_loop_policy(), winloop.WinLoopPolicy)


@app.get("/test")
async def test_get_request():
    return HTMLResponse("<html><body><h1>FAST API WORKS WITH WINLOOP!</h1></body></html>")


# starllete will use asyncio.to_thread() so that this can remain asynchronous
@app.get("/date")
def test_dynamic_response():
    return str(datetime.datetime.now())


# Although tricky to pass and is not normal, it does in fact work...
if __name__ == "__main__":
    winloop.install()
    # Winloop's eventlooppolicy will be passed to uvicorn after this point...
    loop = asyncio.get_event_loop()
    config = uvicorn.Config(app=app,port=10000,loop=loop)
    server = uvicorn.Server(config)
    asyncio.run(server.serve())

How To Use Winloop When Uvloop is not available

# Here's A small Example of using winloop when uvloop is not available to us
import sys
import aiohttp
import asyncio

async def main():
    async with aiohttp.ClientSession("https://httpbin.org") as client:
        async with client.get("/ip") as resp:
            print(await resp.json())

if __name__ == "__main__":
    if sys.platform in ('win32', 'cygwin', 'cli'):
        from winloop import run
    else:
        # if we're on apple or linux do this instead
        from uvloop import run
    run(main())

TODO-List

  • In Winloop 0.2.0 or before 2026 I would like to start planning to migrate to pytest so that things could be formatted a little better. A Migration Python script might be needed or looked into.

  • Update Fastapi Example to a more recent version of fastapi

  • Help Wanted. We're looking for other maintainers to help us.

  • Nightly Builds And Test Suite Workflows for anyone wanting to use newer unreleased versions. (Done, it runs now)

  • Adding in the necessary hooks for pyinstaller to compile this fast library to executable code even though hooks have been known to inflate the size of the .exe files. This is because calling hidden-imports for all the __init__.py modules might annoy some developers. (Luckily I'm aware of this issue because I've been doing this myself...) Update, This is now pending and I hope that it passes

  • write a workflow for nightly builds if necessary for verification of pull requests.

  • Update benchmarks (They are old) can't believe I maintained this project for over a year now...

Videos

Contributing

I put my heart and soul into this library ever since it began and any help is apperciated and means a lot to me, I have other things I wish to explore so every little bit helps

How Can I contribute?

  • I make and branch and make edits and then I do a pull requests before I just step in and add something new. This way you have time to review my additions, changes or feature beforehand.
  • Forking The library.
  • Fixing or editing workflows.
  • Finding and editing spelling mistakes.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

winloop-0.6.2.tar.gz (2.6 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

winloop-0.6.2-cp314-cp314t-win_arm64.whl (576.2 kB view details)

Uploaded CPython 3.14tWindows ARM64

winloop-0.6.2-cp314-cp314t-win_amd64.whl (805.0 kB view details)

Uploaded CPython 3.14tWindows x86-64

winloop-0.6.2-cp314-cp314-win_arm64.whl (547.2 kB view details)

Uploaded CPython 3.14Windows ARM64

winloop-0.6.2-cp314-cp314-win_amd64.whl (655.5 kB view details)

Uploaded CPython 3.14Windows x86-64

winloop-0.6.2-cp313-cp313-win_arm64.whl (528.4 kB view details)

Uploaded CPython 3.13Windows ARM64

winloop-0.6.2-cp313-cp313-win_amd64.whl (644.5 kB view details)

Uploaded CPython 3.13Windows x86-64

winloop-0.6.2-cp312-cp312-win_arm64.whl (529.2 kB view details)

Uploaded CPython 3.12Windows ARM64

winloop-0.6.2-cp312-cp312-win_amd64.whl (644.9 kB view details)

Uploaded CPython 3.12Windows x86-64

winloop-0.6.2-cp311-cp311-win_arm64.whl (530.5 kB view details)

Uploaded CPython 3.11Windows ARM64

winloop-0.6.2-cp311-cp311-win_amd64.whl (647.4 kB view details)

Uploaded CPython 3.11Windows x86-64

winloop-0.6.2-cp310-cp310-win_arm64.whl (528.7 kB view details)

Uploaded CPython 3.10Windows ARM64

winloop-0.6.2-cp310-cp310-win_amd64.whl (640.3 kB view details)

Uploaded CPython 3.10Windows x86-64

winloop-0.6.2-cp39-cp39-win_arm64.whl (530.0 kB view details)

Uploaded CPython 3.9Windows ARM64

winloop-0.6.2-cp39-cp39-win_amd64.whl (641.6 kB view details)

Uploaded CPython 3.9Windows x86-64

winloop-0.6.2-cp38-cp38-win_amd64.whl (645.9 kB view details)

Uploaded CPython 3.8Windows x86-64

File details

Details for the file winloop-0.6.2.tar.gz.

File metadata

  • Download URL: winloop-0.6.2.tar.gz
  • Upload date:
  • Size: 2.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2.tar.gz
Algorithm Hash digest
SHA256 8768fb27c692eb92b2c6921d630bbf1f835738003cd429c0db459fea2d48ce10
MD5 af27ac29ce2ec7a89cf9d42b4b69dc01
BLAKE2b-256 7063e99dd93ab2a7084606cd4deb403c4b87351579e55c28fab3f4861e3de3f7

See more details on using hashes here.

File details

Details for the file winloop-0.6.2-cp314-cp314t-win_arm64.whl.

File metadata

  • Download URL: winloop-0.6.2-cp314-cp314t-win_arm64.whl
  • Upload date:
  • Size: 576.2 kB
  • Tags: CPython 3.14t, Windows ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2-cp314-cp314t-win_arm64.whl
Algorithm Hash digest
SHA256 91217902254ab08ccb093eeb2e25bd5ae85c33c57d0e8708d841ffce5eecc6d6
MD5 a8da98cd988dcf0e19a9f5c69c08dd83
BLAKE2b-256 417c9fac9570a8b8db79dc548225123099bb1ed4673b13a13a5468fc12469cf5

See more details on using hashes here.

File details

Details for the file winloop-0.6.2-cp314-cp314t-win_amd64.whl.

File metadata

  • Download URL: winloop-0.6.2-cp314-cp314t-win_amd64.whl
  • Upload date:
  • Size: 805.0 kB
  • Tags: CPython 3.14t, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2-cp314-cp314t-win_amd64.whl
Algorithm Hash digest
SHA256 9d5460ecade933f50952a6fa172cd447ffdc73180b3762856e0397497fcbc0f3
MD5 3aa848c920d91fec1997c13496e3e326
BLAKE2b-256 5173225f62296a50ecfb245d7d3d4edc765a8866c9e8cede81a27a55c67cb323

See more details on using hashes here.

File details

Details for the file winloop-0.6.2-cp314-cp314-win_arm64.whl.

File metadata

  • Download URL: winloop-0.6.2-cp314-cp314-win_arm64.whl
  • Upload date:
  • Size: 547.2 kB
  • Tags: CPython 3.14, Windows ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2-cp314-cp314-win_arm64.whl
Algorithm Hash digest
SHA256 d529dd253fd70534b7533aa2fbe5914d18ce868e0dc62e68c36fde7b9141b1ad
MD5 4c79c2b08290cc64c6750f964721abc3
BLAKE2b-256 6e16e9ca3ba79ee826db8f740dff643ee6550de25c49f473946eaee9e5e70fbe

See more details on using hashes here.

File details

Details for the file winloop-0.6.2-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: winloop-0.6.2-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 655.5 kB
  • Tags: CPython 3.14, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 2db55a8b3d69fc4a14d58acdc1caa371f76fb424f1923ef573b458dc95b94ac0
MD5 04528a567823985644445d02b0a37319
BLAKE2b-256 a74804f0907048b6b36a2a0ed768cb7913d30b4d3040d2d6207b9a002ef1168c

See more details on using hashes here.

File details

Details for the file winloop-0.6.2-cp313-cp313-win_arm64.whl.

File metadata

  • Download URL: winloop-0.6.2-cp313-cp313-win_arm64.whl
  • Upload date:
  • Size: 528.4 kB
  • Tags: CPython 3.13, Windows ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2-cp313-cp313-win_arm64.whl
Algorithm Hash digest
SHA256 c961b75d7ff315f70f0a22d7bcb2ced0ce5ab781c6546bb6224a7f9082f13fda
MD5 972c5f05e2daa244ee581f4f55946345
BLAKE2b-256 a2379c087a730c5e2637dc7524471a2aa03bda2d0d2d2c99ad269dda4d9ea465

See more details on using hashes here.

File details

Details for the file winloop-0.6.2-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: winloop-0.6.2-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 644.5 kB
  • Tags: CPython 3.13, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 a41d070bbaae29f61f13656d9445c8781eaaa17b15ea8cb047e0ed739a4a53ce
MD5 ae76589f7ba2e7452fb8e09a2f63f9f4
BLAKE2b-256 3e067a15598ea0ed176d8c7ee9aab460b310a7c372768c158c3d5d6b2720a0af

See more details on using hashes here.

File details

Details for the file winloop-0.6.2-cp312-cp312-win_arm64.whl.

File metadata

  • Download URL: winloop-0.6.2-cp312-cp312-win_arm64.whl
  • Upload date:
  • Size: 529.2 kB
  • Tags: CPython 3.12, Windows ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2-cp312-cp312-win_arm64.whl
Algorithm Hash digest
SHA256 78a907cc3d4ac247bce89bf4b6e70d132679428eae725b9f4b92cb7e09aba53e
MD5 a360a836a47eec82b7f853df35bfb825
BLAKE2b-256 e238dca4ae9bba44d98516e0ff46e7598af7a4a03b0fcf1a2c898ab681fd7ab3

See more details on using hashes here.

File details

Details for the file winloop-0.6.2-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: winloop-0.6.2-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 644.9 kB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 90aebf99b34fe20840a26b54ab92ca9a9cb1ab8da278a6c791f83f40a6bfc393
MD5 4b00834c8832a3e3f67bce4c79c3cc3a
BLAKE2b-256 37d736c5a5c544fa49efbe3dc6d938fb30ff69765d49daf5427f12c90d5e4ca4

See more details on using hashes here.

File details

Details for the file winloop-0.6.2-cp311-cp311-win_arm64.whl.

File metadata

  • Download URL: winloop-0.6.2-cp311-cp311-win_arm64.whl
  • Upload date:
  • Size: 530.5 kB
  • Tags: CPython 3.11, Windows ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2-cp311-cp311-win_arm64.whl
Algorithm Hash digest
SHA256 9302bee43708cd44d4eb93bb6a53d6990f48c6ce942d26f0f5cfae421d9f9882
MD5 f9716129531717a4e5a3d683c905f06f
BLAKE2b-256 97ce2ab0671cd9b26caebf036a11054a72b559ca4e04006ff627068302410543

See more details on using hashes here.

File details

Details for the file winloop-0.6.2-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: winloop-0.6.2-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 647.4 kB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 d7b82ac0897d5e82ee4b3def3bebdff238181bf9ab33b3a8c61cf8744ee137cd
MD5 09addd5aad9dd1f27e56c4aee25f3c70
BLAKE2b-256 dc789f1db86b3261bffbc1a2245d685dcf690e2f4532c3437382925012b5ead8

See more details on using hashes here.

File details

Details for the file winloop-0.6.2-cp310-cp310-win_arm64.whl.

File metadata

  • Download URL: winloop-0.6.2-cp310-cp310-win_arm64.whl
  • Upload date:
  • Size: 528.7 kB
  • Tags: CPython 3.10, Windows ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2-cp310-cp310-win_arm64.whl
Algorithm Hash digest
SHA256 2fe075ee5f9f7d28f637414e1060a583e5be38a7f569aeb6f163287f9453f65b
MD5 4e71212a5b5d573dd25b43d54a5d7bc0
BLAKE2b-256 1d76d9a5680c59f74659cce71fdffcb3d95397a0c796fb59da04fd615ed37d1c

See more details on using hashes here.

File details

Details for the file winloop-0.6.2-cp310-cp310-win_amd64.whl.

File metadata

  • Download URL: winloop-0.6.2-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 640.3 kB
  • Tags: CPython 3.10, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 1226c10c17d792f7bdc66d34ff7cadca35a92eb1b1297c1979b0336e3d980f27
MD5 382b9513f7db0d94b19f453b5f2c4bc0
BLAKE2b-256 959d5fbabb936de1fe42bf318c0e1f471445cb7dad1efc38b562d9de8ebaa02d

See more details on using hashes here.

File details

Details for the file winloop-0.6.2-cp39-cp39-win_arm64.whl.

File metadata

  • Download URL: winloop-0.6.2-cp39-cp39-win_arm64.whl
  • Upload date:
  • Size: 530.0 kB
  • Tags: CPython 3.9, Windows ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2-cp39-cp39-win_arm64.whl
Algorithm Hash digest
SHA256 9b7c05fa22a7c9afc790835d09887ab5396a81f89aedaf4e1d5c2ca1e78cbaf4
MD5 df7c9bd1eaa098f6384dc59142f36afc
BLAKE2b-256 1557cb9eda951e7a3042d014d95bcf2527d3901997931f78079cf57556048438

See more details on using hashes here.

File details

Details for the file winloop-0.6.2-cp39-cp39-win_amd64.whl.

File metadata

  • Download URL: winloop-0.6.2-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 641.6 kB
  • Tags: CPython 3.9, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 8084eb199161091a0e0b6ed9409c05c7dbc65fff23a94f7f06166b26b3428cd0
MD5 1d5b7d8b5871407e58b70c8fc3a7d385
BLAKE2b-256 935a08760012e8c64b5b45f82d0a5ef5afc6525cd4d61ea182da3c631a72b08b

See more details on using hashes here.

File details

Details for the file winloop-0.6.2-cp38-cp38-win_amd64.whl.

File metadata

  • Download URL: winloop-0.6.2-cp38-cp38-win_amd64.whl
  • Upload date:
  • Size: 645.9 kB
  • Tags: CPython 3.8, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for winloop-0.6.2-cp38-cp38-win_amd64.whl
Algorithm Hash digest
SHA256 caec1f38d620181adf1ac49fa3c8a3d58844128f4955a23d363e177794f3eb5c
MD5 feff2c9fa7e584fcf8df625651f0f468
BLAKE2b-256 c66843ebef6d4eee8f2f565198fa7bc979ba0b2374b9f05a6e8536d750554d74

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page