• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

STEllAR-GROUP / hpx / #853

19 Dec 2022 01:01AM UTC coverage: 86.287% (+0.4%) from 85.912%
#853

push

StellarBot
Merge #6109

6109: Modernize serialization module r=hkaiser a=hkaiser

- flyby separate serialization of Boost types

working towards https://github.com/STEllAR-GROUP/hpx/issues/5497

Co-authored-by: Hartmut Kaiser <hartmut.kaiser@gmail.com>

53 of 53 new or added lines in 6 files covered. (100.0%)

173939 of 201582 relevant lines covered (86.29%)

1931657.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

48.0
/libs/full/performance_counters/src/counter_creators.cpp
1
//  Copyright (c) 2007-2022 Hartmut Kaiser
2
//
3
//  SPDX-License-Identifier: BSL-1.0
4
//  Distributed under the Boost Software License, Version 1.0. (See accompanying
5
//  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6

7
#include <hpx/config.hpp>
8
#include <hpx/actions/transfer_action.hpp>
9
#include <hpx/actions_base/plain_action.hpp>
10
#include <hpx/agas/agas_fwd.hpp>
11
#include <hpx/assert.hpp>
12
#include <hpx/async_distributed/async.hpp>
13
#include <hpx/async_distributed/transfer_continuation_action.hpp>
14
#include <hpx/components_base/agas_interface.hpp>
15
#include <hpx/functional/function.hpp>
16
#include <hpx/modules/errors.hpp>
17
#include <hpx/naming_base/id_type.hpp>
18
#include <hpx/performance_counters/agas_namespace_action_code.hpp>
19
#include <hpx/performance_counters/counter_creators.hpp>
20
#include <hpx/performance_counters/counters.hpp>
21
#include <hpx/performance_counters/server/component_namespace_counters.hpp>
22
#include <hpx/performance_counters/server/locality_namespace_counters.hpp>
23
#include <hpx/performance_counters/server/primary_namespace_counters.hpp>
24
#include <hpx/performance_counters/server/symbol_namespace_counters.hpp>
25
#include <hpx/type_support/unused.hpp>
26

27
#include <cstdint>
28
#include <string>
29
#include <vector>
30

31
///////////////////////////////////////////////////////////////////////////////
32
namespace hpx { namespace performance_counters {
33

34
    ///////////////////////////////////////////////////////////////////////////
35
    // Creation functions to be registered with counter types
36

37
    /// Default discovery function for performance counters; to be registered
38
    /// with the counter types. It will pass the \a counter_info and the
39
    /// \a error_code to the supplied function.
40
    bool default_counter_discoverer(counter_info const& info,
19✔
41
        discover_counter_func const& f, discover_counters_mode /* mode */,
42
        error_code& ec)
43
    {
44
        return f(info, ec);
19✔
45
    }
46

47
    /// Default discoverer function for performance counters; to be registered
48
    /// with the counter types. It is suitable to be used for all counters
49
    /// following the naming scheme:
50
    ///
51
    ///   /<objectname>{locality#<locality_id>/total}/<instancename>
52
    ///
53
    bool locality_counter_discoverer(counter_info const& info,
60✔
54
        discover_counter_func const& f, discover_counters_mode mode,
55
        error_code& ec)
56
    {
57
        performance_counters::counter_info i = info;
60✔
58

59
        // compose the counter name templates
60
        performance_counters::counter_path_elements p;
60✔
61
        performance_counters::counter_status status =
60✔
62
            get_counter_path_elements(info.fullname_, p, ec);
60✔
63
        if (!status_is_valid(status))
60✔
64
            return false;
×
65

66
        if (mode == discover_counters_mode::minimal ||
60✔
67
            p.parentinstancename_.empty() || p.instancename_.empty())
×
68
        {
69
            if (p.parentinstancename_.empty())
60✔
70
            {
71
                p.parentinstancename_ = "locality#*";
60✔
72
                p.parentinstanceindex_ = -1;
60✔
73
            }
60✔
74

75
            if (p.instancename_.empty())
60✔
76
            {
77
                p.instancename_ = "total";
60✔
78
                p.instanceindex_ = -1;
60✔
79
            }
60✔
80

81
            status = get_counter_name(p, i.fullname_, ec);
60✔
82
            if (!status_is_valid(status) || !f(i, ec) || ec)
60✔
83
                return false;
×
84
        }
60✔
85
        else if (!f(i, ec) || ec)
×
86
        {
87
            return false;
×
88
        }
89

90
        if (&ec != &throws)
60✔
91
            ec = make_success_code();
×
92

93
        return true;
60✔
94
    }
60✔
95

96
    /// Default discoverer function for performance counters; to be registered
97
    /// with the counter types. It is suitable to be used for all counters
98
    /// following the naming scheme:
99
    ///
100
    ///   /<objectname>(locality#<locality_id>/pool#<pool_name>/total)/<instancename>
101
    ///
102
    bool locality_pool_counter_discoverer(counter_info const& info,
1✔
103
        discover_counter_func const& f, discover_counters_mode mode,
104
        error_code& ec)
105
    {
106
        performance_counters::counter_info i = info;
1✔
107

108
        // compose the counter name templates
109
        performance_counters::counter_path_elements p;
1✔
110
        performance_counters::counter_status status =
1✔
111
            get_counter_path_elements(info.fullname_, p, ec);
1✔
112
        if (!status_is_valid(status))
1✔
113
            return false;
×
114

115
        if (mode == discover_counters_mode::minimal ||
1✔
116
            p.parentinstancename_.empty() || p.instancename_.empty())
×
117
        {
118
            if (p.parentinstancename_.empty())
1✔
119
            {
120
                p.parentinstancename_ = "locality#*";
1✔
121
                p.parentinstanceindex_ = -1;
1✔
122
            }
1✔
123

124
            if (p.instancename_.empty())
1✔
125
            {
126
                p.instancename_ = "total";
1✔
127
                p.instanceindex_ = -1;
1✔
128
            }
1✔
129
            else if (p.subinstancename_.empty())
×
130
            {
131
                p.subinstancename_ = "total";
×
132
                p.instanceindex_ = -1;
×
133
            }
×
134

135
            status = get_counter_name(p, i.fullname_, ec);
1✔
136
            if (!status_is_valid(status) || !f(i, ec) || ec)
1✔
137
                return false;
×
138

139
            p.instancename_ = "pool#*";
1✔
140
            p.instanceindex_ = -1;
1✔
141

142
            p.subinstancename_ = "total";
1✔
143
            p.subinstanceindex_ = -1;
1✔
144

145
            status = get_counter_name(p, i.fullname_, ec);
1✔
146
            if (!status_is_valid(status) || !f(i, ec) || ec)
1✔
147
                return false;
×
148
        }
1✔
149
        else if (!f(i, ec) || ec)
×
150
        {
151
            return false;
×
152
        }
153

154
        if (&ec != &throws)
1✔
155
            ec = make_success_code();
×
156

157
        return true;
1✔
158
    }
1✔
159

160
    /// Default discoverer function for AGAS performance counters; to be
161
    /// registered with the counter types. It is suitable to be used for all
162
    /// counters following the naming scheme:
163
    ///
164
    ///   /<objectname>{locality#0/total}/<instancename>
165
    ///
166
    bool locality0_counter_discoverer(counter_info const& info,
28✔
167
        discover_counter_func const& f, discover_counters_mode mode,
168
        error_code& ec)
169
    {
170
        performance_counters::counter_info i = info;
28✔
171

172
        // compose the counter name templates
173
        performance_counters::counter_path_elements p;
28✔
174
        performance_counters::counter_status status =
28✔
175
            get_counter_path_elements(info.fullname_, p, ec);
28✔
176
        if (!status_is_valid(status))
28✔
177
            return false;
×
178

179
        // restrict to locality zero
180
        if (p.parentinstancename_ == "locality#*")
28✔
181
        {
182
            p.parentinstancename_ = "locality";
×
183
            p.parentinstanceindex_ = 0;
×
184
        }
×
185

186
        if (mode == discover_counters_mode::minimal ||
28✔
187
            p.parentinstancename_.empty() || p.instancename_.empty())
×
188
        {
189
            if (p.parentinstancename_.empty())
28✔
190
            {
191
                p.parentinstancename_ = "locality";
28✔
192
                p.parentinstanceindex_ = 0;
28✔
193
            }
28✔
194

195
            if (p.instancename_.empty())
28✔
196
            {
197
                p.instancename_ = "total";
28✔
198
                p.instanceindex_ = -1;
28✔
199
            }
28✔
200
        }
28✔
201

202
        status = get_counter_name(p, i.fullname_, ec);
28✔
203
        if (!status_is_valid(status) || !f(i, ec) || ec)
28✔
204
            return false;
×
205

206
        if (&ec != &throws)
28✔
207
            ec = make_success_code();
×
208

209
        return true;
28✔
210
    }
28✔
211

212
    /// Default discoverer function for performance counters; to be registered
213
    /// with the counter types. It is suitable to be used for all counters
214
    /// following the naming scheme:
215
    ///
216
    ///   /<objectname>{locality#<locality_id>/thread#<threadnum>}/<instancename>
217
    ///
218
    bool locality_thread_counter_discoverer(counter_info const& info,
×
219
        discover_counter_func const& f, discover_counters_mode mode,
220
        error_code& ec)
221
    {
222
        performance_counters::counter_info i = info;
×
223

224
        // compose the counter name templates
225
        performance_counters::counter_path_elements p;
×
226
        performance_counters::counter_status status =
×
227
            get_counter_path_elements(info.fullname_, p, ec);
×
228
        if (!status_is_valid(status))
×
229
            return false;
×
230

231
        if (mode == discover_counters_mode::minimal ||
×
232
            p.parentinstancename_.empty() || p.instancename_.empty())
×
233
        {
234
            if (p.parentinstancename_.empty())
×
235
            {
236
                p.parentinstancename_ = "locality#*";
×
237
                p.parentinstanceindex_ = -1;
×
238
            }
×
239

240
            if (p.instancename_.empty())
×
241
            {
242
                p.instancename_ = "total";
×
243
                p.instanceindex_ = -1;
×
244
            }
×
245

246
            status = get_counter_name(p, i.fullname_, ec);
×
247
            if (!status_is_valid(status) || !f(i, ec) || ec)
×
248
                return false;
×
249

250
            p.instancename_ = "worker-thread#*";
×
251
            p.instanceindex_ = -1;
×
252

253
            status = get_counter_name(p, i.fullname_, ec);
×
254
            if (!status_is_valid(status) || !f(i, ec) || ec)
×
255
                return false;
×
256
        }
×
257
        else if (!f(i, ec) || ec)
×
258
        {
259
            return false;
×
260
        }
261

262
        if (&ec != &throws)
×
263
            ec = make_success_code();
×
264

265
        return true;
×
266
    }
×
267

268
    /// Default discoverer function for performance counters; to be registered
269
    /// with the counter types. It is suitable to be used for all counters
270
    /// following the naming scheme:
271
    ///
272
    ///   /<objectname>{locality#<locality_id>/pool#<pool_name>/thread#<threadnum>}/<instancename>
273
    ///
274
    bool locality_pool_thread_counter_discoverer(counter_info const& info,
11✔
275
        discover_counter_func const& f, discover_counters_mode mode,
276
        error_code& ec)
277
    {
278
        performance_counters::counter_info i = info;
11✔
279

280
        // compose the counter name templates
281
        performance_counters::counter_path_elements p;
11✔
282
        performance_counters::counter_status status =
11✔
283
            get_counter_path_elements(info.fullname_, p, ec);
11✔
284
        if (!status_is_valid(status))
11✔
285
            return false;
×
286

287
        if (mode == discover_counters_mode::minimal ||
12✔
288
            p.parentinstancename_.empty() || p.instancename_.empty() ||
1✔
289
            p.subinstancename_.empty())
1✔
290
        {
291
            if (p.parentinstancename_.empty())
11✔
292
            {
293
                p.parentinstancename_ = "locality#*";
10✔
294
                p.parentinstanceindex_ = -1;
10✔
295
            }
10✔
296

297
            if (p.instancename_.empty())
11✔
298
            {
299
                p.instancename_ = "total";
10✔
300
                p.instanceindex_ = -1;
10✔
301
            }
10✔
302
            else if (p.subinstancename_.empty())
1✔
303
            {
304
                p.subinstancename_ = "total";
1✔
305
                p.instanceindex_ = -1;
1✔
306
            }
1✔
307

308
            status = get_counter_name(p, i.fullname_, ec);
11✔
309
            if (!status_is_valid(status) || !f(i, ec) || ec)
11✔
310
                return false;
×
311

312
            p.instancename_ = "pool#*";
11✔
313
            p.instanceindex_ = -1;
11✔
314

315
            p.subinstancename_ = "worker-thread#*";
11✔
316
            p.subinstanceindex_ = -1;
11✔
317

318
            status = get_counter_name(p, i.fullname_, ec);
11✔
319
            if (!status_is_valid(status) || !f(i, ec) || ec)
11✔
320
                return false;
×
321
        }
11✔
322
        else if (!f(i, ec) || ec)
×
323
        {
324
            return false;
×
325
        }
326

327
        if (&ec != &throws)
11✔
328
            ec = make_success_code();
×
329

330
        return true;
11✔
331
    }
11✔
332

333
    /// Default discoverer function for performance counters; to be registered
334
    /// with the counter types. It is suitable to be used for all counters
335
    /// following the naming scheme:
336
    ///
337
    ///   /<objectname>{locality#<locality_id>/pool#<poolname>/thread#<threadnum>}/<instancename>
338
    ///
339
    /// This is essentially the same as above just that locality#*/total is not
340
    /// supported.
341
    bool locality_pool_thread_no_total_counter_discoverer(
2✔
342
        counter_info const& info, discover_counter_func const& f,
343
        discover_counters_mode mode, error_code& ec)
344
    {
345
        performance_counters::counter_info i = info;
2✔
346

347
        // compose the counter name templates
348
        performance_counters::counter_path_elements p;
2✔
349
        performance_counters::counter_status status =
2✔
350
            get_counter_path_elements(info.fullname_, p, ec);
2✔
351
        if (!status_is_valid(status))
2✔
352
            return false;
×
353

354
        if (mode == discover_counters_mode::minimal ||
2✔
355
            p.parentinstancename_.empty() || p.instancename_.empty() ||
×
356
            p.subinstancename_.empty())
×
357
        {
358
            if (p.parentinstancename_.empty())
2✔
359
            {
360
                p.parentinstancename_ = "locality#*";
2✔
361
                p.parentinstanceindex_ = -1;
2✔
362
            }
2✔
363

364
            p.instancename_ = "pool#*";
2✔
365
            p.instanceindex_ = -1;
2✔
366

367
            p.subinstancename_ = "worker-thread#*";
2✔
368
            p.subinstanceindex_ = -1;
2✔
369

370
            status = get_counter_name(p, i.fullname_, ec);
2✔
371
            if (!status_is_valid(status) || !f(i, ec) || ec)
2✔
372
                return false;
×
373
        }
2✔
374
        else if (!f(i, ec) || ec)
×
375
        {
376
            return false;
×
377
        }
378

379
        if (&ec != &throws)
2✔
380
            ec = make_success_code();
×
381

382
        return true;
2✔
383
    }
2✔
384

385
    /// Default discoverer function for performance counters; to be registered
386
    /// with the counter types. It is suitable to be used for all counters
387
    /// following the naming scheme:
388
    ///
389
    ///   /<objectname>(locality#<locality_id>/numa#<threadnum>)/<instancename>
390
    ///
391
    bool locality_numa_counter_discoverer(counter_info const& info,
×
392
        discover_counter_func const& f, discover_counters_mode mode,
393
        error_code& ec)
394
    {
395
        performance_counters::counter_info i = info;
×
396

397
        // compose the counter name templates
398
        performance_counters::counter_path_elements p;
×
399
        performance_counters::counter_status status =
×
400
            get_counter_path_elements(info.fullname_, p, ec);
×
401
        if (!status_is_valid(status))
×
402
            return false;
×
403

404
        if (mode == discover_counters_mode::minimal ||
×
405
            p.parentinstancename_.empty() || p.instancename_.empty())
×
406
        {
407
            if (p.parentinstancename_.empty())
×
408
            {
409
                p.parentinstancename_ = "locality#*";
×
410
                p.parentinstanceindex_ = -1;
×
411
            }
×
412

413
            if (p.instancename_.empty())
×
414
            {
415
                p.instancename_ = "total";
×
416
                p.instanceindex_ = -1;
×
417
            }
×
418

419
            status = get_counter_name(p, i.fullname_, ec);
×
420
            if (!status_is_valid(status) || !f(i, ec) || ec)
×
421
                return false;
×
422

423
            p.instancename_ = "numa-node#*";
×
424
            p.instanceindex_ = -1;
×
425

426
            status = get_counter_name(p, i.fullname_, ec);
×
427
            if (!status_is_valid(status) || !f(i, ec) || ec)
×
428
                return false;
×
429
        }
×
430
        else if (!f(i, ec) || ec)
×
431
        {
432
            return false;
×
433
        }
434

435
        if (&ec != &throws)
×
436
            ec = make_success_code();
×
437

438
        return true;
×
439
    }
×
440

441
    ///////////////////////////////////////////////////////////////////////////
442
    /// Creation function for raw counters. The passed function is encapsulating
443
    /// the actual value to monitor. This function checks the validity of the
444
    /// supplied counter name, it has to follow the scheme:
445
    ///
446
    ///   /<objectname>{locality#<locality_id>/total}/<instancename>
447
    ///
448
    naming::gid_type locality_raw_counter_creator(counter_info const& info,
1✔
449
        hpx::function<std::int64_t(bool)> const& f, error_code& ec)
450
    {
451
        // verify the validity of the counter instance name
452
        counter_path_elements paths;
1✔
453
        get_counter_path_elements(info.fullname_, paths, ec);
1✔
454
        if (ec)
1✔
455
            return naming::invalid_gid;
×
456

457
        if (paths.parentinstance_is_basename_)
1✔
458
        {
459
            HPX_THROWS_IF(ec, hpx::error::bad_parameter,
×
460
                "locality_raw_counter_creator",
461
                "invalid counter instance parent name: " +
462
                    paths.parentinstancename_);
463
            return naming::invalid_gid;
×
464
        }
465

466
        if (paths.instancename_ == "total" && paths.instanceindex_ == -1)
1✔
467
            return detail::create_raw_counter(
1✔
468
                info, f, ec);    // overall counter
1✔
469

470
        HPX_THROWS_IF(ec, hpx::error::bad_parameter,
×
471
            "locality_raw_counter_creator",
472
            "invalid counter instance name: " + paths.instancename_);
473
        return naming::invalid_gid;
×
474
    }
1✔
475

476
    naming::gid_type locality_raw_values_counter_creator(
1✔
477
        counter_info const& info,
478
        hpx::function<std::vector<std::int64_t>(bool)> const& f, error_code& ec)
479
    {
480
        // verify the validity of the counter instance name
481
        counter_path_elements paths;
1✔
482
        get_counter_path_elements(info.fullname_, paths, ec);
1✔
483
        if (ec)
1✔
484
            return naming::invalid_gid;
×
485

486
        if (paths.parentinstance_is_basename_)
1✔
487
        {
488
            HPX_THROWS_IF(ec, hpx::error::bad_parameter,
×
489
                "locality_raw_values_counter_creator",
490
                "invalid counter instance parent name: " +
491
                    paths.parentinstancename_);
492
            return naming::invalid_gid;
×
493
        }
494

495
        if (paths.instancename_ == "total" && paths.instanceindex_ == -1)
1✔
496
        {
497
            return detail::create_raw_counter(
1✔
498
                info, f, ec);    // overall counter
1✔
499
        }
500

501
        HPX_THROWS_IF(ec, hpx::error::bad_parameter,
×
502
            "locality_raw_values_counter_creator",
503
            "invalid counter instance name: " + paths.instancename_);
504
        return naming::invalid_gid;
×
505
    }
1✔
506

507
    namespace detail {
508

509
        naming::gid_type retrieve_agas_counter(std::string const& name,
×
510
            hpx::id_type const& agas_id, error_code& ec)
511
        {
512
            naming::gid_type id;
×
513

514
#if !defined(HPX_COMPUTE_DEVICE_CODE)
515
            //  get action code from counter type
516
            agas::namespace_action_code service_code =
×
517
                agas::detail::retrieve_action_service_code(name, ec);
×
518
            if (agas::invalid_request == service_code)
×
519
                return id;
×
520

521
            switch (service_code)
×
522
            {
523
            case agas::component_ns_statistics_counter:
524
            {
525
                agas::component_namespace_statistics_counter_action action;
526
                return action(agas_id, name).get_gid();
×
527
            }
528
            case agas::locality_ns_statistics_counter:
529
            {
530
                agas::locality_namespace_statistics_counter_action action;
531
                return action(agas_id, name).get_gid();
×
532
            }
533
            case agas::symbol_ns_statistics_counter:
534
            {
535
                agas::symbol_namespace_statistics_counter_action action;
536
                return action(agas_id, name).get_gid();
×
537
            }
538
            case agas::primary_ns_statistics_counter:
539
            {
540
                agas::primary_namespace_statistics_counter_action action;
541
                return action(agas_id, name).get_gid();
×
542
            }
543
            default:
544
                HPX_THROWS_IF(ec, hpx::error::bad_parameter,
×
545
                    "retrieve_statistics_counter",
546
                    "unknown counter agas counter name: " + name);
547
                break;
×
548
            }
549
            return id;
×
550
#else
551
            HPX_ASSERT(false);
552
            HPX_UNUSED(name);
553
            HPX_UNUSED(agas_id);
554
            HPX_UNUSED(ec);
555
            return id;
556
#endif
557
        }
×
558
    }    // namespace detail
559

560
    ///////////////////////////////////////////////////////////////////////////
561
    /// Creation function for raw AGAS counters. This function checks the
562
    /// validity of the supplied counter name, it has to follow the scheme:
563
    ///
564
    ///   /agas(<objectinstance>/total)/<instancename>
565
    ///
566
    naming::gid_type agas_raw_counter_creator(counter_info const& info,
×
567
        error_code& ec, char const* const service_name)
568
    {
569
        // verify the validity of the counter instance name
570
        counter_path_elements paths;
×
571
        get_counter_path_elements(info.fullname_, paths, ec);
×
572
        if (ec)
×
573
            return naming::invalid_gid;
×
574

575
        if (paths.objectname_ != "agas")
×
576
        {
577
            HPX_THROWS_IF(ec, hpx::error::bad_parameter,
×
578
                "agas_raw_counter_creator",
579
                "unknown performance counter (unrelated to AGAS)");
580
            return naming::invalid_gid;
×
581
        }
582
        if (paths.parentinstance_is_basename_)
×
583
        {
584
            HPX_THROWS_IF(ec, hpx::error::bad_parameter,
×
585
                "agas_raw_counter_creator",
586
                "invalid counter instance parent name: " +
587
                    paths.parentinstancename_);
588
            return naming::invalid_gid;
×
589
        }
590

591
        // counter instance name: <agas_instance_name>/total
592
        // for instance: locality#0/total
593
        if (paths.instancename_ == "total" && paths.instanceindex_ == -1)
×
594
        {
595
            // find the referenced AGAS instance and dispatch the request there
596
            std::string service(agas::service_name);
×
597
            service += paths.parentinstancename_;
×
598

599
            if (-1 == paths.parentinstanceindex_)
×
600
            {
601
                HPX_THROWS_IF(ec, hpx::error::bad_parameter,
×
602
                    "agas_raw_counter_creator",
603
                    "invalid parent instance index: -1");
604
                return naming::invalid_gid;
×
605
            }
606
            service += "#";
×
607
            service += std::to_string(paths.parentinstanceindex_);
×
608

609
            service += "/";
×
610
            service += service_name;
×
611

612
            hpx::id_type id = agas::resolve_name(launch::sync, service, ec);
×
613
            if (id == hpx::invalid_id)
×
614
            {
615
                HPX_THROWS_IF(ec, hpx::error::not_implemented,
×
616
                    "agas_raw_counter_creator",
617
                    "invalid counter name: " +
618
                        remove_counter_prefix(info.fullname_));
619
                return naming::invalid_gid;
×
620
            }
621

622
            return detail::retrieve_agas_counter(info.fullname_, id, ec);
×
623
        }
×
624

625
        HPX_THROWS_IF(ec, hpx::error::not_implemented,
×
626
            "agas_raw_counter_creator",
627
            "invalid counter type name: " + paths.instancename_);
628
        return naming::invalid_gid;
×
629
    }
×
630
}}    // namespace hpx::performance_counters
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc