3.9.1 • Published 5 days ago

@observertc/client-monitor-js v3.9.1

Weekly downloads
-
License
Apache-2.0
Repository
github
Last release
5 days ago

Javascript library to monitor WebRTC applications

@observertc/client-monitor-js is a client side library to monitor WebRTCStats and to integrate your app to observertc components.

Table of Contents:

Qucik Start

Install it from npm package repository.

npm i @observertc/client-monitor-js

Use it in your WebRTC application.

import { ClientMonitor } from "@observertc/client-monitor-js";
// see full config in Configuration section
const config = {
    collectingPeriodInMs: 5000,
};
const monitor = ClientMonitor.create(config);
monitor.addStatsCollector({
    id: "collectorId",
    getStats: () => peerConnection.getStats(),
});

monitor.events.onStatsCollected(() => {
    const storage = monitor.storage;
    for (const inboundRtp of storage.inboundRtps()) {
        const trackId = inboundRtp.getTrackId();
        const remoteOutboundRtp = inboundRtp.getRemoteOutboundRtp();
        console.log(trackId, inboundRtp.stats, remoteOutboundRtp.stats);
    }
})

The above example collect stats in every 5s. When stats are collected the inboundRtp entries are iterated. The stats of the inboound-rtp, its corresponded trackId and remote outbound stats are logged.

Client Monitor Storage

Client Monitor collects WebRTCStats. The collected stats can be accessed through entries the client monitor storage provides.

Storage Navigations

Entries:

inbound-rtp entries

const storage = monitor.storage;

for (const inboundRtp of storage.inboundRtps()) {
    const receiver = inboundRtp.getReceiver();
    const trackId = inboundRtp.getTrackId();
    const ssrc = inboundRtp.getSsrc();
    const remoteOutboundRtp = inboundRtp.getRemoteOutboundRtp();
    const peerConnection = inboundRtp.getPeerConnection();
    const transport = inboundRtp.getTransport();
    const codec = inboundRtp.getCodec();

    console.log(trackId, ssrc, 
        inboundRtp.stats, 
        remoteOutboundRtp.stats, 
        receiver.stats,
        peerConnection.stats,
        transport.stats,
        codec.stats
    );
}

outbound-rtp entries

const storage = monitor.storage;

for (const outboundRtp of storage.outboundRtps()) {
    const sender = outboundRtp.getSender();
    const trackId = outboundRtp.getTrackId();
    const ssrc = outboundRtp.getSsrc();
    const remoteInboundRtp = outboundRtp.getRemoteInboundRtp();
    const peerConnection = outboundRtp.getPeerConnection();
    const transport = outboundRtp.getTransport();
    const mediaSource = outboundRtp.getMediaSource();

    console.log(trackId, ssrc, 
        outboundRtp.stats, 
        remoteInboundRtp.stats, 
        sender.stats,
        peerConnection.stats,
        transport.stats,
        mediaSource.stats
    );
}

remote-inbound-rtp entries

const storage = monitor.storage;

for (const remoteInboundRtp of storage.remoteInboundRtps()) {
    const ssrc = remoteInboundRtp.getSsrc();
    const outboundRtp = remoteInboundRtp.getOutboundRtp();
    const peerConnection = remoteInboundRtp.getPeerConnection();

    console.log(ssrc, 
        remoteInboundRtp.stats, 
        outboundRtp.stats, 
        peerConnection.stats
    );
}

remote-outbound-rtp entries

const storage = monitor.storage;

for (const remoteOutboundRtp of storage.remoteOutboundRtps()) {
    const ssrc = remoteOutboundRtp.getSsrc();
    const inboundRtp = remoteOutboundRtp.getInboundRtp();
    const peerConnection = remoteOutboundRtp.getPeerConnection();

    console.log(ssrc, 
        remoteOutboundRtp.stats, 
        inboundRtp.stats, 
        peerConnection.stats
    );
}

media-sources entries

const storage = monitor.storage;

for (const mediaSource of storage.mediaSources()) {
    const peerConnection = mediaSource.getPeerConnection();

    console.log( 
        mediaSource.stats, 
        peerConnection.stats
    );
}

contributing-source entries

const storage = monitor.storage;

for (const cssrc of storage.contributingSources()) {
    const peerConnection = cssrc.getPeerConnection();

    console.log( 
        cssrc.stats, 
        peerConnection.stats
    );
}

data-channel entries

const storage = monitor.storage;

for (const dataChannel of storage.dataChannels()) {
    const peerConnection = dataChannel.getPeerConnection();

    console.log( 
        dataChannel.stats, 
        peerConnection.stats
    );
}

transceiver entries

const storage = monitor.storage;

for (const transceiver of storage.transceivers()) {
    const receiver = transceiver.getReceiver();
    const sender = transceiver.getSender();
    const peerConnection = transceiver.getPeerConnection();

    console.log( 
        transceiver.stats, 
        receiver.stats,
        sender.stats,
        peerConnection.stats
    );
}

sender entries

const storage = monitor.storage;

for (const sender of storage.senders()) {
    const mediaSource = sender.getMediaSource();
    const peerConnection = sender.getPeerConnection();

    console.log( 
        sender.stats,
        mediaSource.stats,
        peerConnection.stats
    );
}

receiver entries

const storage = monitor.storage;

for (const receiver of storage.receivers()) {
    const peerConnection = receiver.getPeerConnection();

    console.log( 
        receiver.stats,
        peerConnection.stats
    );
}

transport entries

const storage = monitor.storage;

for (const transport of storage.transports()) {
    const contributingTransport = transport.getRtcpTransport();
    const selectedIceCandidatePair = transport.getSelectedIceCandidatePair();
    const localCandidate = transport.getLocalCertificate();
    const remoteCandidate = transport.getRemoteCertificate();
    const peerConnection = transport.getPeerConnection();

    console.log( 
        transport.stats,
        contributingTransport?.stats,
        selectedIceCandidatePair?.stats,
        localCandidate?.stats,
        remoteCandidate?.stats,
        remoteCandidate?.stats
    );
}

sctp transport entries

const storage = monitor.storage;

for (const sctpTransport of storage.sctpTransports()) {
    const transport = sctpTransport.getTransport();
    const peerConnection = sctpTransport.getPeerConnection();

    console.log( 
        sctpTransport.stats,
        transport?.stats,
        peerConnection?.stats,
    );
}

ice candidate pair entries

const storage = monitor.storage;

for (const iceCandidatePair of storage.iceCandidatePairs()) {
    const transport = iceCandidatePair.getTransport();
    const localCandidate = iceCandidatePair.getLocalCandidate();
    const remoteCandidate = iceCandidatePair.getRemoteCandidate();
    const peerConnection = iceCandidatePair.getPeerConnection();

    console.log( 
        iceCandidatePair.stats,
        transport?.stats,
        localCandidate?.stats,
        remoteCandidate?.stats,
        peerConnection?.stats,
    );
}

local ice candidate entries

const storage = monitor.storage;

for (const localCandidate of storage.localCandidates()) {
    const transport = localCandidate.getTransport();
    const peerConnection = localCandidate.getPeerConnection();

    console.log( 
        localCandidate.stats,
        peerConnection?.stats,
    );
}

remote ice candidate entries

const storage = monitor.storage;

for (const remoteCandidate of storage.remoteCandidates()) {
    const transport = remoteCandidate.getTransport();
    const peerConnection = remoteCandidate.getPeerConnection();

    console.log( 
        remoteCandidate.stats,
        peerConnection?.stats,
    );
}

certificate entries

const storage = monitor.storage;

for (const certificate of storage.certificates()) {
    const peerConnection = certificate.getPeerConnection();

    console.log( 
        certificate.stats,
        peerConnection?.stats,
    );
}

ice server entries

const storage = monitor.storage;

for (const iceServer of storage.iceServers()) {
    const peerConnection = iceServer.getPeerConnection();

    console.log( 
        iceServer.stats,
        peerConnection?.stats,
    );
}

peer connection entries

const storage = monitor.storage;

for (const peerConnection of storage.peerConnections()) {

    for (const codec of peerConnection.getCodecs()) 
        console.log(
            `peerConnection(${peerConnection.id}).codec(${codec.id}).stats: `, 
            codec.stats
        );

    for (const inboundRtp of peerConnection.inboundRtps()) 
        console.log(
            `peerConnection(${peerConnection.id}).inboundRtp(${inboundRtp.id}).stats: `, 
            inboundRtp.stats
        );

    for (const outboundRtp of peerConnection.outboundRtps()) 
        console.log(
            `peerConnection(${peerConnection.id}).outboundRtp(${outboundRtp.id}).stats: `, 
            outboundRtp.stats
        );

    for (const remoteInboundRtp of peerConnection.remoteInboundRtps()) 
        console.log(
            `peerConnection(${peerConnection.id}).remoteInboundRtp(${remoteInboundRtp.id}).stats: `, 
            remoteInboundRtp.stats
        );

    for (const remoteOutboundRtp of peerConnection.remoteOutboundRtps()) 
        console.log(
            `peerConnection(${peerConnection.id}).remoteOutboundRtp(${remoteOutboundRtp.id}).stats: `, 
            remoteOutboundRtp.stats
        );

    for (const mediaSource of peerConnection.mediaSources()) 
        console.log(
            `peerConnection(${peerConnection.id}).mediaSource(${mediaSource.id}).stats: `, 
            mediaSource.stats
        );

    for (const cssrc of peerConnection.contributingSources()) 
        console.log(
            `peerConnection(${peerConnection.id}).cssrc(${cssrc.id}).stats: `, 
            cssrc.stats
        );

    for (const dataChannel of peerConnection.dataChannels()) 
        console.log(
            `peerConnection(${peerConnection.id}).dataChannel(${dataChannel.id}).stats: `, 
            dataChannel.stats
        );

    for (const transceiver of peerConnection.transceivers()) 
        console.log(
            `peerConnection(${peerConnection.id}).transceiver(${transceiver.id}).stats: `, 
            transceiver.stats
        );

    for (const sender of peerConnection.senders()) 
        console.log(
            `peerConnection(${peerConnection.id}).sender(${sender.id}).stats: `, 
            sender.stats
        );

    for (const receiver of peerConnection.receivers()) 
        console.log(
            `peerConnection(${peerConnection.id}).receiver(${receiver.id}).stats: `, 
            receiver.stats
        );

    for (const transport of peerConnection.transports()) 
        console.log(
            `peerConnection(${peerConnection.id}).transport(${transport.id}).stats: `, 
            transport.stats
        );

    for (const sctpTransport of peerConnection.sctpTransports()) 
        console.log(
            `peerConnection(${peerConnection.id}).sctpTransport(${sctpTransport.id}).stats: `, 
            sctpTransport.stats
        );

    for (const iceCandidate of peerConnection.iceCandidatePairs()) 
        console.log(
            `peerConnection(${peerConnection.id}).iceCandidate(${iceCandidate.id}).stats: `, 
            iceCandidate.stats
        );

    for (const localCandidate of peerConnection.localCandidates()) 
        console.log(
            `peerConnection(${peerConnection.id}).localCandidate(${localCandidate.id}).stats: `, 
            localCandidate.stats
        );

    for (const remoteCandidate of peerConnection.remoteCandidates()) 
        console.log(
            `peerConnection(${peerConnection.id}).remoteCandidate(${remoteCandidate.id}).stats: `, 
            remoteCandidate.stats
        );

    for (const certificate of peerConnection.certificates()) 
        console.log(
            `peerConnection(${peerConnection.id}).certificate(${certificate.id}).stats: `, 
            certificate.stats
        );

    for (const iceServer of peerConnection.iceServers()) 
        console.log(
            `peerConnection(${peerConnection.id}).iceServer(${iceServer.id}).stats: `, 
            iceServer.stats
        );

    console.log(`peerConnection(${peerConnection.id}) trackIds:`, 
            Array.from(peerConnection.trackIds())
        );
}

Connect to Observer

The client-monitor can be connected to an Observer.

import { ClientMontior } from "@observertc/client-monitor-js";
// see full config in Configuration section
const config = {
    collectingPeriodInMs: 5000,
    samplingPeriodInMs: 10000,
    sendingPeriodInMs: 15000,
    sampler: {
        roomId: "testRoom",
    },
    sender: {
        websocket: {
            urls: ["ws://localhost:7080/samples/myServiceId/myMediaUnitId"]
        }
    }
};
const monitor = ClientMontior.create(config);
monitor.addStatsCollector({
    id: "collectorId",
    getStats: () => peerConnection.getStats(),
});

The stats are collected in every 5s, but samples are only made in every 10s. Samples are sent to the observer in every 15s.

Configurations

const config = {
    /**
     * By setting it, the monitor calls the added statsCollectors periodically
     * and pulls the stats.
     * 
     * DEFAULT: undefined
     */
    collectingPeriodInMs: 5000,
    /**
     * By setting it, the monitor make samples periodically.
     * 
     * DEFAULT: undefined
     */
    samplingPeriodInMs: 10000,

    /**
     * By setting it, the monitor sends the samples periodically.
     * 
     * DEFAULT: undefined
     */
    sendingPeriodInMs: 10000,

    /**
     * By setting it stats items and entries are deleted if they are not updated.
     * 
     * DEFAULT: undefined
     */
    statsExpirationTimeInMs: 60000,

    /**
     * Collector Component related configurations
     * 
     * DEFAULT: configured by the monitor
     */
    collectors: {
        /**
         * Sets the adapter adapt different browser type and version 
         * provided stats.
         * 
         * DEFAULT: configured by the monitor
         */
        adapter: {
            /**
             * the type of the browser, e.g.: chrome, firefox, safari
             * 
             * DEFAULT: configured by the collector
             */
            browserType: "chrome",
            /**
             * the version of the browser, e.g.: 97.xx.xxxxx
             * 
             * DEFAULT: configured by the collector
             */
            browserVersion: "97.1111.111",
        },
    },

    /**
     * Sampling Component Related configurations
     * 
     */
    sampler: {
        /**
         * The identifier of the room the clients are in.
         * 
         * If server side componet is used to collect the samples, this parameter is the critical to provide to match clients being in the same room.
         * 
         * DEFAULT: a generated unique value
         * 
         * NOTE: if this value has not been set clients which are in the same room will not be matched at the monitor
         */
        roomId: "testRoom",

        /**
         * The identifier of the client. If it is not provided, then it a UUID is generated. If it is provided it must be a valid UUID.
         * 
         * DEFAULT: a generated unique value
         */
        clientId: "clientId",

        /**
         * the identifier of the call between clients in the same room. If not given then the server side assigns one. If it is given it must be a valid UUID.
         * 
         * DEFAULT: undefined
         */
        callId: "callId",
        /**
         * The userId of the client appeared to other users.
         * 
         * DEFAULT: undefined
         */
        userId: "testUser",

        /**
         * Indicate if the sampler only sample stats updated since the last sampling.
         * 
         * DEFAULT: true
         */
        incrementalSampling: true,
    },
    /**
     * Configure the sender component.
     */
    sender: {
        /**
         * Configure the format used to transport samples or receieve 
         * feedback from the server.
         * 
         * Possible values: json, protobuf
         * 
         * DEFAULT: json
         * 
         */
        format: "json",
         /**
         * Websocket configuration to transport the samples
         */
        websocket: {
            /**
             * Target urls in a priority order. If the Websocket has not succeeded for the first,
             * it tries with the second. If no more url left the connection is failed
             * 
             */
            urls: ["ws://localhost:7080/samples/myServiceId/myMediaUnitId"],
            /**
             * The maximum number of retries to connect to a server before,
             * tha connection failed is stated.
             * 
             * DEFAULT: 3
             */
            maxRetries: 1,
        }
    }
};

Examples

Calculate video tracks Fps

Assuming you have a configured and running monitor and a collector you added to poll the stats from peer connection, here is an example to calculate the frame per sec for tracks.

const monitor = //.. defined above
monitor.onStatsCollected(() => {
    const now = Date.now();
    for (const inboundRtp of monitor.storage.inboundRtps()) {
        const trackId = inboundRtp.getTrackId();
        const SSRC = inboundRtp.getSsrc();
        const traceId = `${trackId}-${SSRC}`;
        // lets extract what we need from the stats for inboundRtp: https://www.w3.org/TR/webrtc-stats/#inboundrtpstats-dict*
        const { framesReceived, kind } = inboundRtp.stats;
        if (kind !== "video") continue;
        const trace = traces.get(traceId);
        if (!trace) {
            traces.set(traceId, {
                framesReceived,
                timestamp: now,
            });
            continue;
        }
        const elapsedTimeInS = (now - trace.timestamp) / 1000;
        const fps = (framesReceived - trace.framesReceived) / elapsedTimeInS;
        const peerConnectionId = inboundRtp.getPeerConnection()?.id;
        trace.framesReceived = framesReceived;
        trace.timestamp = now;

        console.log(`On peerConnection: ${peerConnectionId}, track ${trackId}, SSRC: ${SSRC} the FPS is ${fps}`);
    }
});

Collect RTT measurements for peer connections

const monitor = //.. defined above
monitor.onStatsCollected(() => {
    const RTTs = new Map();
    for (const outboundRtp of monitor.storage.outboundRtps()) {
        const remoteInboundRtp = outboundRtp.getRemoteInboundRtp();
        const { roundTripTime } = remoteInboundRtp.stats;
        const peerConnectionId = outboundRtp.getPeerConnection()?.collectorId;
        let measurements = results.get(peerConnectionId);
        if (!measurements) {
            measurements = [];
            RTTs.set(peerConnectionId, measurements);
        }
        measurements.push(roundTripTime);
    }
    // here you have the RTT measurements groupped by peer connections
    console.log(Array.from(RTTs.entries()));
});

NPM package

https://www.npmjs.com/package/@observertc/client-monitor-js

API docs

https://observertc.github.io/client-monitor-js/modules/ClientMonitor.html

Schemas

https://github.com/observertc/schemas

Getting Involved

Client-monitor is made with the intention to provide an open-source monitoring solution for WebRTC developers. We develop new features and maintaining the current product with the help of the community. If you are interested in getting involved please read our contribution guideline.

License

Apache-2.0

3.9.1

12 days ago

3.9.2-b2398ba.0

12 days ago

3.10.1-aae84be.0

12 days ago

3.9.1-a3ab476.0

14 days ago

3.9.0

13 days ago

3.9.1-b23498b.0

13 days ago

3.10.1-bcf6017.0

13 days ago

3.9.1-0307b09.0

13 days ago

3.9.1-0cebb7b.0

13 days ago

3.9.1-e1dd132.0

13 days ago

3.9.1-e9e5088.0

13 days ago

3.9.1-6449176.0

13 days ago

3.9.1-023644e.0

14 days ago

3.9.1-4ebd0bb.0

15 days ago

3.9.1-35f0ab4.0

16 days ago

3.8.1-992d274.0

17 days ago

3.9.1-5829c78.0

17 days ago

3.8.0

17 days ago

3.7.0

21 days ago

3.8.1-c8a3f4d.0

21 days ago

3.7.1-4987a98.0

22 days ago

3.7.1-f13debd.0

22 days ago

3.7.1-8c3b6b1.0

22 days ago

3.7.1-a183d68.0

24 days ago

3.7.1-98b0e4f.0

25 days ago

3.7.1-072850e.0

25 days ago

3.7.1-ebd559e.0

1 month ago

3.6.1-21fec8b.0

1 month ago

3.7.1-f149ff9.0

1 month ago

3.6.0

1 month ago

3.7.1-b36fc63.0

1 month ago

3.6.1-09388d2.0

2 months ago

3.6.1-67ba628.0

2 months ago

3.6.1-df49e42.0

2 months ago

3.5.1-5df3eaa.0

2 months ago

3.5.0

2 months ago

3.6.1-faf98ee.0

2 months ago

3.5.1-aa4f3f9.0

2 months ago

3.5.1-0dc6346.0

2 months ago

3.5.1-f3b5e4b.0

2 months ago

3.5.1-8d6523b.0

2 months ago

3.5.1-2a0ddb5.0

2 months ago

3.5.1-f214d03.0

2 months ago

3.5.1-541fab5.0

2 months ago

3.5.1-ac793db.0

2 months ago

3.5.1-894548e.0

2 months ago

3.4.1-701d1dd.0

3 months ago

3.4.0

3 months ago

3.5.1-e0736f6.0

3 months ago

3.5.1-fdd297c.0

3 months ago

3.4.1-a0f1059.0

3 months ago

3.4.1-e6caa47.0

3 months ago

3.4.1-9570c11.0

3 months ago

3.3.1-5a88dce.0

4 months ago

3.3.1

4 months ago

3.0.1-8711fd1.0

10 months ago

3.2.0

7 months ago

3.3.1-b3d1802.0

7 months ago

3.2.1-37c1567.0

7 months ago

3.3.1-7ca8981.0

7 months ago

3.0.1-e3de48c.0

10 months ago

3.1.0

8 months ago

3.2.1-422fc30.0

8 months ago

3.1.1-98a03f9.0

8 months ago

3.1.1-4540b56.0

8 months ago

3.3.1-a331b40.0

7 months ago

2.2.3

10 months ago

2.2.2

10 months ago

3.0.1-a589884.0

10 months ago

3.0.0

10 months ago

3.3.1-7795cec.0

7 months ago

3.3.1-1dd6213.0

7 months ago

3.3.1-caa5949.0

7 months ago

3.3.1-2b10ca8.0

7 months ago

3.1.1-4a5dd6e.0

8 months ago

3.0.1-f21a60e.0

10 months ago

3.3.0

6 months ago

3.1.1-41f2839.0

8 months ago

3.3.1-aaac59d.0

6 months ago

3.1.1-1fd6090.0

8 months ago

3.0.1-86bd990.0

10 months ago

3.0.1-a512ab9.0

10 months ago

2.1.5-199e734.0

12 months ago

2.1.5-30a0af0.0

12 months ago

2.1.5-90c2ad0.0

11 months ago

2.1.5-ff790a6.0

12 months ago

2.1.5-675d7b1.0

12 months ago

2.1.5-369988a.0

12 months ago

2.1.5-4b9ec73.0

12 months ago

2.1.5-2c1f640.0

12 months ago

2.1.5-5356534.0

12 months ago

2.1.5-fb3799e.0

12 months ago

2.2.1

11 months ago

2.2.0

11 months ago

2.1.5-44e44f2.0

12 months ago

2.1.5-7662362.0

12 months ago

2.1.5-05a8fc0.0

1 year ago

2.1.5-195c4c5.0

12 months ago

2.1.5-4f58445.0

12 months ago

2.1.5-4ee3a7e.0

12 months ago

2.2.1-271620b.0

11 months ago

2.1.5-541f90e.0

12 months ago

2.1.5-d984d79.0

1 year ago

2.1.5-6952ed2.0

1 year ago

2.1.5-16f3e72.0

12 months ago

2.0.1-5678cfd.0

1 year ago

2.0.1-bce474b.0

1 year ago

2.0.1-2391a29.0

1 year ago

2.0.1-6eb834a.0

1 year ago

2.0.1-df00bf3.0

1 year ago

2.0.1-8dfd576.0

1 year ago

2.0.1

1 year ago

2.0.0

1 year ago

2.0.1-0505e4e.0

1 year ago

2.0.1-3670e43.0

1 year ago

2.0.1-2c18c70.0

1 year ago

1.3.2

1 year ago

2.1.2

1 year ago

2.1.1

1 year ago

2.1.4

1 year ago

2.1.3

1 year ago

2.1.0

1 year ago

2.0.1-1c93da5.0

1 year ago

2.0.1-2d13853.0

1 year ago

2.0.1-rc.0

1 year ago

2.0.1-7617dbb.0

1 year ago

1.3.2-next.0

1 year ago

1.3.1

1 year ago

1.3.2-rc.0

1 year ago

1.3.0

1 year ago

1.2.0

2 years ago

1.1.1

2 years ago

1.2.1

2 years ago

1.1.0

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago

1.0.0-beta.44

2 years ago

1.0.0-beta.45

2 years ago

1.0.0-beta.48

2 years ago

1.0.0-beta.46

2 years ago

1.0.0-beta.47

2 years ago

1.0.0-beta.43

2 years ago

1.0.0-beta.42

2 years ago

1.0.0-beta.41

2 years ago

1.0.0-beta.40

2 years ago

1.0.0-beta.39

2 years ago

1.0.0-beta.38

2 years ago

1.0.0-beta.37

2 years ago

1.0.0-beta.36

2 years ago

1.0.0-beta.35

2 years ago

1.0.0-beta.34

2 years ago

1.0.0-beta.33

2 years ago

1.0.0-beta.32

2 years ago

1.0.0-beta.31

2 years ago

1.0.0-beta.30

2 years ago

1.0.0-beta.28

2 years ago

1.0.0-beta.23

2 years ago

1.0.0-beta.22

2 years ago

1.0.0-beta.21

2 years ago

1.0.0-beta.20

2 years ago