1
0
mirror of https://github.com/ohwgiles/laminar.git synced 2024-10-27 20:34:20 +00:00

Compare commits

..

No commits in common. "master" and "1.2" have entirely different histories.
master ... 1.2

22 changed files with 120 additions and 461 deletions

View File

@ -1,5 +1,5 @@
###
### Copyright 2015-2024 Oliver Giles
### Copyright 2015-2021 Oliver Giles
###
### This file is part of Laminar
###
@ -16,45 +16,8 @@
### You should have received a copy of the GNU General Public License
### along with Laminar. If not, see <http://www.gnu.org/licenses/>
###
cmake_minimum_required(VERSION 3.6)
project(laminar)
if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
# ld.lld is a default option on FreeBSD
set(LLVM_LINKER_IS_LLD ON)
endif()
# ld.lld specific options. There is no sane way in cmake
# to detect if toolchain is actually using ld.lld
if (LLVM_LINKER_IS_LLD)
if (NOT DEFINED LINKER_EMULATION_FLAGS)
if (${CMAKE_SYSTEM_PROCESSOR} STREQUAL "amd64")
set(LINKER_EMULATION_FLAGS "-melf_x86_64")
elseif (${CMAKE_SYSTEM_PROCESSOR} STREQUAL "x86_64")
set(LINKER_EMULATION_FLAGS "-melf_x86_64")
elseif (${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64")
set(LINKER_EMULATION_FLAGS "-maarch64elf")
elseif (${CMAKE_SYSTEM_PROCESSOR} STREQUAL "powerpc64le")
set(LINKER_EMULATION_FLAGS "-melf64lppc")
elseif (${CMAKE_SYSTEM_PROCESSOR} STREQUAL "powerpc64")
set(LINKER_EMULATION_FLAGS "-melf64ppc")
elseif (${CMAKE_SYSTEM_PROCESSOR} STREQUAL "riscv64")
# llvm17 & riscv64 requires extra step, it is necessary to
# patch 'Elf64.e_flags' (48-th byte) in binary-blob object files
# with value 0x5 - to change soft_float ABI to hard_float ABI
# so they can link with rest of the object files.
set(LINKER_EMULATION_FLAGS "-melf64lriscv")
elseif (${CMAKE_SYSTEM_PROCESSOR} STREQUAL "arm")
set(LINKER_EMULATION_FLAGS "-marmelf")
elseif (${CMAKE_SYSTEM_PROCESSOR} STREQUAL "armv7")
set(LINKER_EMULATION_FLAGS "-marmelf")
else()
message(FATAL_ERROR
"Unsupported '${CMAKE_SYSTEM_PROCESSOR}' translation to emulation flag. "
"Please set it explicitly 'cmake -DLINKER_EMULATION_FLAGS=\"-melf_your_arch\" ...'")
endif()
endif()
endif()
cmake_minimum_required(VERSION 3.6)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_CXX_STANDARD 17)
@ -93,7 +56,7 @@ macro(generate_compressed_bins BASEDIR)
DEPENDS ${BASEDIR}/${FILE}
)
add_custom_command(OUTPUT ${OUTPUT_FILE}
COMMAND ${CMAKE_LINKER} ${LINKER_EMULATION_FLAGS} -r -b binary -o ${OUTPUT_FILE} ${COMPRESSED_FILE}
COMMAND ${CMAKE_LINKER} -r -b binary -o ${OUTPUT_FILE} ${COMPRESSED_FILE}
COMMAND ${CMAKE_OBJCOPY}
--rename-section .data=.rodata.alloc,load,readonly,data,contents
--add-section .note.GNU-stack=/dev/null
@ -121,11 +84,11 @@ add_custom_command(OUTPUT index_html_size.h
# Download 3rd-party frontend JS libs...
file(DOWNLOAD https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.min.js
${CMAKE_BINARY_DIR}/js/vue.min.js EXPECTED_MD5 fb192338844efe86ec759a40152fcb8e)
js/vue.min.js EXPECTED_MD5 fb192338844efe86ec759a40152fcb8e)
file(DOWNLOAD https://raw.githubusercontent.com/drudru/ansi_up/v4.0.4/ansi_up.js
${CMAKE_BINARY_DIR}/js/ansi_up.js EXPECTED_MD5 b31968e1a8fed0fa82305e978161f7f5)
file(DOWNLOAD https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js
${CMAKE_BINARY_DIR}/js/Chart.min.js EXPECTED_MD5 7dd5ea7d2cf22a1c42b43c40093d2669)
js/ansi_up.js EXPECTED_MD5 b31968e1a8fed0fa82305e978161f7f5)
file(DOWNLOAD https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js
js/Chart.min.js EXPECTED_MD5 f6c8efa65711e0cbbc99ba72997ecd0e)
# ...and compile them
generate_compressed_bins(${CMAKE_BINARY_DIR} js/vue.min.js
js/ansi_up.js js/Chart.min.js)
@ -146,31 +109,13 @@ set(LAMINARD_CORE_SOURCES
index_html_size.h
)
find_package(CapnProto REQUIRED)
include_directories(${CAPNP_INCLUDE_DIRS})
find_package(SQLite3 REQUIRED)
include_directories(${SQLite3_INCLUDE_DIRS})
find_package(ZLIB REQUIRED)
include_directories(${ZLIB_INCLUDE_DIRS})
find_package(Threads REQUIRED)
include_directories(${Threads_INCLUDE_DIRS})
## Server
add_executable(laminard ${LAMINARD_CORE_SOURCES} src/main.cpp ${COMPRESSED_BINS})
target_link_libraries(laminard CapnProto::capnp-rpc CapnProto::capnp CapnProto::kj-http CapnProto::kj-async
CapnProto::kj Threads::Threads SQLite::SQLite3 ZLIB::ZLIB)
if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
pkg_check_modules(INOTIFY REQUIRED libinotify)
target_link_libraries(laminard ${INOTIFY_LINK_LIBRARIES})
endif()
target_link_libraries(laminard capnp-rpc capnp kj-http kj-async kj pthread sqlite3 z)
## Client
add_executable(laminarc src/client.cpp src/version.cpp laminar.capnp.c++)
target_link_libraries(laminarc CapnProto::capnp-rpc CapnProto::capnp CapnProto::kj-async CapnProto::kj Threads::Threads)
target_link_libraries(laminarc capnp-rpc capnp kj-async kj pthread)
## Manpages
macro(gzip SOURCE)
@ -194,6 +139,7 @@ if(BUILD_TESTS)
target_link_libraries(laminar-tests ${GTEST_LIBRARIES} capnp-rpc capnp kj-http kj-async kj pthread sqlite3 z)
endif()
set(SYSTEMD_UNITDIR /lib/systemd/system CACHE PATH "Path to systemd unit files")
set(BASH_COMPLETIONS_DIR /usr/share/bash-completion/completions CACHE PATH "Path to bash completions directory")
set(ZSH_COMPLETIONS_DIR /usr/share/zsh/site-functions CACHE PATH "Path to zsh completions directory")
install(TARGETS laminard RUNTIME DESTINATION sbin)
@ -202,8 +148,5 @@ install(FILES etc/laminar.conf DESTINATION /etc)
install(FILES etc/laminarc-completion.bash DESTINATION ${BASH_COMPLETIONS_DIR} RENAME laminarc)
install(FILES etc/laminarc-completion.zsh DESTINATION ${ZSH_COMPLETIONS_DIR} RENAME _laminarc)
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(SYSTEMD_UNITDIR /lib/systemd/system CACHE PATH "Path to systemd unit files")
configure_file(etc/laminar.service.in laminar.service @ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/laminar.service DESTINATION ${SYSTEMD_UNITDIR})
endif()
configure_file(etc/laminar.service.in laminar.service @ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/laminar.service DESTINATION ${SYSTEMD_UNITDIR})

View File

@ -12,11 +12,11 @@ See [the website](https://laminar.ohwg.net) and the [documentation](https://lami
First install development packages for `capnproto (version 0.7.0 or newer)`, `rapidjson`, `sqlite` and `boost` (for the header-only `multi_index_container` library) from your distribution's repository or other source.
On Debian Bookworm, this can be done with:
On Debian Bullseye, this can be done with:
```bash
sudo apt install capnproto cmake g++ libboost-dev libcapnp-dev libsqlite3-dev \
make rapidjson-dev zlib1g-dev pkg-config
sudo apt install \
capnproto cmake g++ libboost-dev libcapnp-dev libsqlite3-dev rapidjson-dev zlib1g-dev
```
Then compile and install laminar with:

View File

@ -534,7 +534,7 @@ If `CONTEXTS` is empty or absent (or if `JOB.conf` doesn't exist), laminar will
## Adding environment to a context
Append desired environment variables to `/var/lib/laminar/cfg/contexts/CONTEXT_NAME.env`:
Append desired environment variables to `/var/lib/laminar/cfg/contexts/CONTEXT_NAME.conf`:
```
DUT_IP=192.168.3.2

View File

@ -7,7 +7,7 @@ Documentation=https://laminar.ohwg.net/docs.html
[Service]
User=laminar
EnvironmentFile=-/etc/laminar.conf
ExecStart=@CMAKE_INSTALL_PREFIX@/sbin/laminard -v
ExecStart=@CMAKE_INSTALL_PREFIX@/sbin/laminard
[Install]
WantedBy=multi-user.target

View File

@ -9,7 +9,7 @@ set -x
# Simple way of getting the docker build tag:
tag=$(docker build -q - <<\EOF
FROM debian:bookworm
FROM debian:bullseye
RUN apt-get update && apt-get install -y build-essential
EOF
)
@ -19,7 +19,7 @@ EOF
exec {pfd}<><(:) # get a new pipe
docker build - <<\EOF |
FROM debian:bookworm
FROM debian:bullseye
RUN apt-get update && apt-get install -y build-essential
EOF
tee >(awk '/Successfully built/{print $3}' >&$pfd) # parse output to pipe

View File

@ -4,10 +4,10 @@ OUTPUT_DIR=$PWD
SOURCE_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]})/..)
VERSION=$(cd "$SOURCE_DIR" && git describe --tags --abbrev=8 --dirty)-1~upstream-debian11
VERSION=$(cd "$SOURCE_DIR" && git describe --tags --abbrev=8 --dirty)-1~upstream-debian10
DOCKER_TAG=$(docker build -q - <<EOS
FROM debian:11-slim
FROM debian:10-slim
RUN apt-get update && apt-get install -y wget cmake g++ capnproto libcapnp-dev rapidjson-dev libsqlite3-dev libboost-dev zlib1g-dev
EOS
)

View File

@ -4,10 +4,10 @@ OUTPUT_DIR=$PWD
SOURCE_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]})/..)
VERSION=$(cd "$SOURCE_DIR" && git describe --tags --abbrev=8 --dirty)-1~upstream-debian11
VERSION=$(cd "$SOURCE_DIR" && git describe --tags --abbrev=8 --dirty)-1~upstream-debian10
DOCKER_TAG=$(docker build -q - <<EOS
FROM debian:11-slim
FROM debian:10-slim
RUN dpkg --add-architecture armhf && apt-get update && apt-get install -y wget cmake crossbuild-essential-armhf capnproto libcapnp-dev:armhf rapidjson-dev libsqlite3-dev:armhf libboost-dev:armhf zlib1g-dev:armhf
EOS
)

View File

@ -1,50 +0,0 @@
#!/bin/bash -e
set -ex
OUTPUT_DIR=$PWD
SOURCE_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]})/..)
VERSION=$(cd "$SOURCE_DIR" && git describe --tags --abbrev=8 --dirty)-1~upstream-debian12
DOCKER_TAG=$(docker build -q - <<EOS
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y wget cmake g++ capnproto libcapnp-dev rapidjson-dev libsqlite3-dev libboost-dev zlib1g-dev pkg-config
EOS
)
docker run --rm -i -v $SOURCE_DIR:/laminar:ro -v $OUTPUT_DIR:/output $DOCKER_TAG bash -xe <<EOS
mkdir /build
cd /build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DLAMINAR_VERSION=$VERSION -DZSH_COMPLETIONS_DIR=/usr/share/zsh/functions/Completion/Unix /laminar
make -j4
mkdir laminar
make DESTDIR=laminar install/strip
mkdir laminar/DEBIAN
cat <<EOF > laminar/DEBIAN/control
Package: laminar
Version: $VERSION
Section:
Priority: optional
Architecture: amd64
Maintainer: Oliver Giles <web ohwg net>
Depends: libcapnp-1.0.1, libsqlite3-0, zlib1g
Description: Lightweight Continuous Integration Service
EOF
echo /etc/laminar.conf > laminar/DEBIAN/conffiles
cat <<EOF > laminar/DEBIAN/postinst
#!/bin/bash
echo Creating laminar user with home in /var/lib/laminar
useradd -r -d /var/lib/laminar -s /usr/sbin/nologin laminar
mkdir -p /var/lib/laminar/cfg/{jobs,contexts,scripts}
chown -R laminar: /var/lib/laminar
EOF
chmod +x laminar/DEBIAN/postinst
dpkg-deb --build laminar
mv laminar.deb /output/laminar_${VERSION}_amd64.deb
EOS

View File

@ -1,50 +0,0 @@
#!/bin/bash -e
set -ex
OUTPUT_DIR=$PWD
SOURCE_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]})/..)
VERSION=$(cd "$SOURCE_DIR" && git describe --tags --abbrev=8 --dirty)-1~upstream-debian13
DOCKER_TAG=$(docker build -q - <<EOS
FROM debian:trixie-slim
RUN apt-get update && apt-get install -y wget cmake g++ capnproto libcapnp-dev rapidjson-dev libsqlite3-dev libboost-dev zlib1g-dev pkg-config
EOS
)
docker run --rm -i -v $SOURCE_DIR:/laminar:ro -v $OUTPUT_DIR:/output $DOCKER_TAG bash -xe <<EOS
mkdir /build
cd /build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DLAMINAR_VERSION=$VERSION -DZSH_COMPLETIONS_DIR=/usr/share/zsh/functions/Completion/Unix /laminar
make -j4
mkdir laminar
make DESTDIR=laminar install/strip
mkdir laminar/DEBIAN
cat <<EOF > laminar/DEBIAN/control
Package: laminar
Version: $VERSION
Section:
Priority: optional
Architecture: amd64
Maintainer: Oliver Giles <web ohwg net>
Depends: libcapnp-1.0.1, libsqlite3-0, zlib1g
Description: Lightweight Continuous Integration Service
EOF
echo /etc/laminar.conf > laminar/DEBIAN/conffiles
cat <<EOF > laminar/DEBIAN/postinst
#!/bin/bash
echo Creating laminar user with home in /var/lib/laminar
useradd -r -d /var/lib/laminar -s /usr/sbin/nologin laminar
mkdir -p /var/lib/laminar/cfg/{jobs,contexts,scripts}
chown -R laminar: /var/lib/laminar
EOF
chmod +x laminar/DEBIAN/postinst
dpkg-deb --build laminar
mv laminar.deb /output/laminar_${VERSION}_amd64.deb
EOS

View File

@ -1,49 +0,0 @@
#!/bin/bash -e
OUTPUT_DIR=$PWD
SOURCE_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]})/..)
VERSION=$(cd "$SOURCE_DIR" && git describe --tags --abbrev=8 --dirty)-1~upstream-ubuntu2204
DOCKER_TAG=$(docker build -q - <<EOS
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y wget cmake g++ capnproto libcapnp-dev rapidjson-dev libsqlite3-dev libboost-dev zlib1g-dev pkg-config
EOS
)
docker run --rm -i -v $SOURCE_DIR:/laminar:ro -v $OUTPUT_DIR:/output $DOCKER_TAG bash -xe <<EOS
mkdir /build
cd /build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DLAMINAR_VERSION=$VERSION -DZSH_COMPLETIONS_DIR=/usr/share/zsh/functions/Completion/Unix /laminar
make -j4
mkdir laminar
make DESTDIR=laminar install/strip
mkdir laminar/DEBIAN
cat <<EOF > laminar/DEBIAN/control
Package: laminar
Version: $VERSION
Section:
Priority: optional
Architecture: amd64
Maintainer: Oliver Giles <web ohwg net>
Depends: libcapnp-0.8.0, libsqlite3-0, zlib1g
Description: Lightweight Continuous Integration Service
EOF
echo /etc/laminar.conf > laminar/DEBIAN/conffiles
cat <<EOF > laminar/DEBIAN/postinst
#!/bin/bash
echo Creating laminar user with home in /var/lib/laminar
useradd -r -d /var/lib/laminar -s /usr/sbin/nologin laminar
mkdir -p /var/lib/laminar/cfg/{jobs,contexts,scripts}
chown -R laminar: /var/lib/laminar
EOF
chmod +x laminar/DEBIAN/postinst
dpkg-deb --build laminar
mv laminar.deb /output/laminar_${VERSION}_amd64.deb
EOS

View File

@ -1,49 +0,0 @@
#!/bin/bash -e
OUTPUT_DIR=$PWD
SOURCE_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]})/..)
VERSION=$(cd "$SOURCE_DIR" && git describe --tags --abbrev=8 --dirty)-1~upstream-ubuntu2404
DOCKER_TAG=$(docker build -q - <<EOS
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y wget cmake g++ capnproto libcapnp-dev rapidjson-dev libsqlite3-dev libboost-dev zlib1g-dev pkg-config
EOS
)
docker run --rm -i -v $SOURCE_DIR:/laminar:ro -v $OUTPUT_DIR:/output $DOCKER_TAG bash -xe <<EOS
mkdir /build
cd /build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DLAMINAR_VERSION=$VERSION -DZSH_COMPLETIONS_DIR=/usr/share/zsh/functions/Completion/Unix /laminar
make -j4
mkdir laminar
make DESTDIR=laminar install/strip
mkdir laminar/DEBIAN
cat <<EOF > laminar/DEBIAN/control
Package: laminar
Version: $VERSION
Section:
Priority: optional
Architecture: amd64
Maintainer: Oliver Giles <web ohwg net>
Depends: libcapnp-1.0.1, libsqlite3-0, zlib1g
Description: Lightweight Continuous Integration Service
EOF
echo /etc/laminar.conf > laminar/DEBIAN/conffiles
cat <<EOF > laminar/DEBIAN/postinst
#!/bin/bash
echo Creating laminar user with home in /var/lib/laminar
useradd -r -d /var/lib/laminar -s /usr/sbin/nologin laminar
mkdir -p /var/lib/laminar/cfg/{jobs,contexts,scripts}
chown -R laminar: /var/lib/laminar
EOF
chmod +x laminar/DEBIAN/postinst
dpkg-deb --build laminar
mv laminar.deb /output/laminar_${VERSION}_amd64.deb
EOS

View File

@ -21,7 +21,6 @@
#include <sqlite3.h>
#include <string.h>
#include <math.h>
#include <cstdint>
struct StdevCtx {
double mean;

View File

@ -131,16 +131,9 @@ kj::Promise<void> Http::cleanupPeers(kj::Timer& timer)
{
return timer.afterDelay(15 * kj::SECONDS).then([&]{
for(EventPeer* p : eventPeers) {
// Even single threaded, if load causes this timeout to be serviced
// before writeEvents has created a fulfiller, or if an exception
// caused the destruction of the promise but attach(peer) hasn't yet
// removed it from the eventPeers list, we will see a null fulfiller
// here
if(p->fulfiller) {
// an empty SSE message is a colon followed by two newlines
p->pendingOutput.push_back(":\n\n");
p->fulfiller->fulfill();
}
// an empty SSE message is a colon followed by two newlines
p->pendingOutput.push_back(":\n\n");
p->fulfiller->fulfill();
}
return cleanupPeers(timer);
}).eagerlyEvaluate(nullptr);

View File

@ -95,31 +95,11 @@ Laminar::Laminar(Server &server, Settings settings) :
db = new Database((homePath/"laminar.sqlite").toString(true).cStr());
// Prepare database for first use
// TODO: error handling
const char *create_table_stmt =
"CREATE TABLE IF NOT EXISTS builds("
"name TEXT, number INT UNSIGNED, node TEXT, queuedAt INT, "
"startedAt INT, completedAt INT, result INT, output TEXT, "
"outputLen INT, parentJob TEXT, parentBuild INT, reason TEXT, "
"PRIMARY KEY (name, number DESC))";
db->exec(create_table_stmt);
// Migrate from (name, number) primary key to (name, number DESC).
// SQLite does not allow to alter primary key of existing table, so
// we have to create a new table.
db->stmt("SELECT sql LIKE '%, PRIMARY KEY (name, number))' "
"FROM sqlite_master WHERE type = 'table' AND name = 'builds'")
.fetch<int>([&](int has_old_index) {
if (has_old_index) {
LLOG(INFO, "Migrating table to the new primary key");
db->exec("BEGIN TRANSACTION");
db->exec("ALTER TABLE builds RENAME TO builds_old");
db->exec(create_table_stmt);
db->exec("INSERT INTO builds SELECT * FROM builds_old");
db->exec("DROP TABLE builds_old");
db->exec("COMMIT");
}
});
db->exec("CREATE TABLE IF NOT EXISTS builds("
"name TEXT, number INT UNSIGNED, node TEXT, queuedAt INT, "
"startedAt INT, completedAt INT, result INT, output TEXT, "
"outputLen INT, parentJob TEXT, parentBuild INT, reason TEXT, "
"PRIMARY KEY (name, number))");
db->exec("CREATE INDEX IF NOT EXISTS idx_completion_time ON builds("
"completedAt DESC)");
@ -358,8 +338,9 @@ std::string Laminar::getStatus(MonitorScope scope) {
j.set("description", desc == jobDescriptions.end() ? "" : desc->second);
} else if(scope.type == MonitorScope::ALL) {
j.startArray("jobs");
db->stmt("SELECT name, number, startedAt, completedAt, result, reason "
"FROM builds GROUP BY name HAVING number = MAX(number)")
db->stmt("SELECT name,number,startedAt,completedAt,result,reason FROM builds b "
"JOIN (SELECT name n,MAX(number) latest FROM builds WHERE result IS NOT NULL GROUP BY n) q "
"ON b.name = q.n AND b.number = latest")
.fetch<str,uint,time_t,time_t,int,str>([&](str name,uint number, time_t started, time_t completed, int result, str reason){
j.StartObject();
j.set("name", name);

View File

@ -21,11 +21,7 @@
#include <unistd.h>
#include <queue>
#include <dirent.h>
#if defined(__FreeBSD__)
#include <sys/procctl.h>
#else
#include <sys/prctl.h>
#endif
#include <sys/types.h>
#include <sys/wait.h>
#include <kj/async-io.h>
@ -321,11 +317,7 @@ int leader_main(void) {
// will be reparented to this one instead of init (or higher layer subreaper).
// We do this so that the run will wait until all descedents exit before executing
// the next step.
#if defined(__FreeBSD__)
procctl(P_PID, 0, PROC_REAP_ACQUIRE, NULL);
#else
prctl(PR_SET_CHILD_SUBREAPER, 1, NULL, NULL, NULL);
#endif
// Become the leader of a new process group. This is so that all child processes
// will also get a kill signal when the run is aborted

View File

@ -26,7 +26,6 @@
#include <kj/async-unix.h>
#include <kj/filesystem.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
@ -54,13 +53,6 @@ static void usage(std::ostream& out) {
out << " -v enable verbose output\n";
}
static void on_sighup(int)
{
constexpr const char msg[] = "Laminar received and ignored SIGHUP\n";
// write(2) is safe to call inside signal handler.
write(STDERR_FILENO, msg, sizeof(msg) - 1);
}
int main(int argc, char** argv) {
if(argv[0][0] == '{')
return leader_main();
@ -100,7 +92,6 @@ int main(int argc, char** argv) {
signal(SIGINT, &laminar_quit);
signal(SIGTERM, &laminar_quit);
signal(SIGHUP, &on_sighup);
printf("laminard version %s started\n", laminar_version());

View File

@ -158,7 +158,7 @@
<template id="run"><div style="display: grid; grid-template-rows: auto 1fr">
<div style="padding: 15px">
<div style="display: grid; grid-template-columns: auto 25px auto auto 1fr 400px; gap: 5px; align-items: center">
<h2 style="white-space: nowrap"><span v-html="runIcon(job.result)"></span> <router-link :to="'jobs/'+route.params.name">{{route.params.name}}</router-link> #{{route.params.number}}</h2>
<h2 style="white-space: nowrap"><span v-html="runIcon(job.result)"></span> {{route.params.name}} #{{route.params.number}}</h2>
<span></span>
<router-link :disabled="route.params.number == 1" :to="'jobs/'+route.params.name+'/'+(route.params.number-1)" tag="button">&laquo;</router-link>
<router-link :disabled="route.params.number == latestNum" :to="'jobs/'+route.params.name+'/'+(parseInt(route.params.number)+1)" tag="button">&raquo;</router-link>

View File

@ -129,8 +129,7 @@ const Charts = (() => {
}]
},
options: {
hover: { mode: null },
aspectRatio: 2
hover: { mode: null }
}
});
c.executorBusyChanged = busy => {
@ -160,28 +159,20 @@ const Charts = (() => {
datasets: [{
label: 'Failed Builds',
backgroundColor: "#883d3d",
data: data.map(e => e.failed || 0),
fill: true,
tension: 0.35,
data: data.map(e => e.failed || 0)
},{
label: 'Successful Builds',
backgroundColor: "#74af77",
data: data.map(e => e.success || 0),
fill: true,
tension: 0.35,
data: data.map(e => e.success || 0)
}]
},
options:{
plugins: {
title: { display: true, text: 'Runs per day' },
tooltip:{callbacks:{title: (tip) => dayNames[tip[0].dataIndex].long}},
},
scales: {
y: {
ticks:{callback: (label, index, labels) => Number.isInteger(label) ? label: null},
stacked: true
},
},
title: { display: true, text: 'Runs per day' },
tooltips:{callbacks:{title: (tip, data) => dayNames[tip[0].index].long}},
scales:{yAxes:[{
ticks:{userCallback: (label, index, labels) => Number.isInteger(label) ? label: null},
stacked: true
}]}
}
});
c.jobCompleted = success => {
@ -192,7 +183,7 @@ const Charts = (() => {
},
createRunsPerJobChart: (id, data) => {
const c = new Chart(document.getElementById("chartBpj"), {
type: 'bar',
type: 'horizontalBar',
data: {
labels: Object.keys(data),
datasets: [{
@ -202,16 +193,9 @@ const Charts = (() => {
}]
},
options:{
indexAxis: 'y',
plugins: {
title: { display: true, text: 'Runs per job' },
},
title: { display: true, text: 'Runs per job' },
hover: { mode: null },
scales: {
x: {
ticks:{callback: (label, index, labels)=> Number.isInteger(label) ? label: null}
}
}
scales:{xAxes:[{ticks:{userCallback: (label, index, labels)=> Number.isInteger(label) ? label: null}}]}
}
});
c.jobCompleted = name => {
@ -232,7 +216,7 @@ const Charts = (() => {
createTimePerJobChart: (id, data, completedCounts) => {
const scale = timeScale(Math.max(...Object.values(data)));
const c = new Chart(document.getElementById(id), {
type: 'bar',
type: 'horizontalBar',
data: {
labels: Object.keys(data),
datasets: [{
@ -242,23 +226,18 @@ const Charts = (() => {
}]
},
options:{
indexAxis: 'y',
plugins: {
title: { display: true, text: 'Mean run time this week' },
tooltip:{callbacks:{
label: (tip) => tip.dataset.label + ': ' + tip.raw.toFixed(2) + ' ' + scale.label.toLowerCase()
}}
},
title: { display: true, text: 'Mean run time this week' },
hover: { mode: null },
scales: {
x:{
ticks: {callback: scale.ticks},
title: {
display: true,
text: scale.label
}
scales:{xAxes:[{
ticks:{userCallback: scale.ticks},
scaleLabel: {
display: true,
labelString: scale.label
}
},
}]},
tooltips:{callbacks:{
label: (tip, data) => data.datasets[tip.datasetIndex].label + ': ' + tip.xLabel.toFixed(2) + ' ' + scale.label.toLowerCase()
}}
}
});
c.jobCompleted = (name, time) => {
@ -282,8 +261,7 @@ const Charts = (() => {
label: name,
data: durations.map(x => x * scale.factor),
borderColor: 'hsl('+(name.hashCode() % 360)+', 27%, 57%)',
backgroundColor: 'transparent',
tension: 0.35,
backgroundColor: 'transparent'
});
const c = new Chart(document.getElementById(id), {
type: 'line',
@ -292,21 +270,21 @@ const Charts = (() => {
datasets: data.map(e => dataValue(e.name, e.durations))
},
options:{
plugins: {
legend: { display: true, position: 'bottom' },
title: { display: true, text: 'Run time changes' },
tooltip: { enabled: false },
},
title: { display: true, text: 'Run time changes' },
legend:{ display: true, position: 'bottom' },
scales:{
x: {ticks: {display: false}},
y: {
ticks: {callback: scale.ticks},
title: {
xAxes:[{ticks:{display: false}}],
yAxes:[{
ticks:{userCallback: scale.ticks},
scaleLabel: {
display: true,
text: scale.label
labelString: scale.label
}
}
}]
},
tooltips:{
enabled:false
}
}
});
c.jobCompleted = (name, time) => {
@ -332,65 +310,62 @@ const Charts = (() => {
data: {
labels: jobs.map(e => '#' + e.number).reverse(),
datasets: [{
label: 'Average',
type: 'line',
data: [{x:0, y:avg * scale.factor}, {x:1, y:avg * scale.factor}],
borderColor: '#7483af',
backgroundColor: 'transparent',
xAxisID: 'avg',
pointRadius: 0,
pointHitRadius: 0,
pointHoverRadius: 0,
},{
label: 'Build time',
backgroundColor: jobs.map(e => e.result == 'success' ? '#74af77': '#883d3d').reverse(),
barPercentage: 1.0,
categoryPercentage: 0.95,
data: jobs.map(e => (e.completed - e.started) * scale.factor).reverse()
}]
},
options: {
plugins: {
title: { display: true, text: 'Build time' },
tooltip: {
callbacks:{
label: (tip) => scale.ticks(tip.raw) + ' ' + scale.label.toLowerCase()
}
}
},
title: { display: true, text: 'Build time' },
hover: { mode: null },
scales:{
x: {
grid: {
xAxes:[{
categoryPercentage: 0.95,
barPercentage: 1.0
},{
id: 'avg',
type: 'linear',
ticks: {
display: false
},
gridLines: {
display: false,
drawBorder: false
}
},
y: {
suggestedMax: avg * scale.factor,
ticks: {callback: scale.ticks },
title: {display: true, text: scale.label}
}
}],
yAxes:[{
ticks:{userCallback: scale.ticks},
scaleLabel:{display: true, labelString: scale.label}
}]
},
},
plugins: [{
afterDraw: (chart, args, options) => {
const {ctx, avg, chartArea, scales:{y:yaxis}} = chart;
const y = chartArea.top + yaxis.height - avg * scale.factor * yaxis.height / yaxis.end;
ctx.save();
ctx.beginPath();
ctx.translate(chartArea.left, y);
ctx.moveTo(0,0);
ctx.lineTo(chartArea.width, 0);
ctx.lineWidth = 2;
ctx.strokeStyle = '#7483af';
ctx.stroke();
ctx.restore();
}
}]
tooltips:{callbacks:{
label: (tip, data) => scale.ticks(tip.yLabel) + ' ' + scale.label.toLowerCase()
}}
}
});
c.avg = avg;
c.jobCompleted = (num, result, time) => {
c.avg = ((c.avg * (num - 1)) + time) / num;
c.options.scales.y.suggestedMax = avg * scale.factor;
if(c.data.datasets[0].data.length == 20) {
let avg = c.data.datasets[0].data[0].y / scale.factor;
avg = ((avg * (num - 1)) + time) / num;
c.data.datasets[0].data[0].y = avg * scale.factor;
c.data.datasets[0].data[1].y = avg * scale.factor;
if(c.data.datasets[1].data.length == 20) {
c.data.labels.shift();
c.data.datasets[0].data.shift();
c.data.datasets[0].backgroundColor.shift();
c.data.datasets[1].data.shift();
c.data.datasets[1].backgroundColor.shift();
}
c.data.labels.push('#' + num);
c.data.datasets[0].data.push(time * scale.factor);
c.data.datasets[0].backgroundColor.push(result == 'success' ? '#74af77': '#883d3d');
c.data.datasets[1].data.push(time * scale.factor);
c.data.datasets[1].backgroundColor.push(result == 'success' ? '#74af77': '#883d3d');
c.update();
};
return c;
@ -399,11 +374,13 @@ const Charts = (() => {
})();
// For all charts, set miniumum Y to 0
Chart.defaults.scales.linear.suggestedMin = 0;
Chart.scaleService.updateScaleDefaults('linear', {
ticks: { suggestedMin: 0 }
});
// Don't display legend by default
Chart.defaults.plugins.legend.display = false;
Chart.defaults.global.legend.display = false;
// Disable tooltip hover animations
Chart.defaults.plugins.tooltip.animation = false;
Chart.defaults.global.hover.animationDuration = 0;
// Component for the / endpoint
const Home = templateId => {
@ -505,13 +482,12 @@ const All = templateId => {
state.jobsRunning = msg.running;
// mix running and completed jobs
msg.running.forEach(job => {
job.result = 'running';
const idx = state.jobs.findIndex(j => j.name === job.name);
if (idx > -1)
state.jobs[idx] = job;
else {
// special case: first run of a job.
state.jobs.unshift(job);
state.jobs.unshift(j);
state.jobs.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
}
});
@ -567,7 +543,7 @@ const All = templateId => {
if (expr)
ret = state.jobs.filter(job => (new RegExp(expr)).test(job.name));
else
ret = [...state.jobs];
ret = state.jobs;
// sort failed before success, newest first
ret.sort((a,b) => a.result == b.result ? a.started - b.started : 2*(b.result == 'success')-1);
return ret;

View File

@ -122,7 +122,7 @@ a.active:hover { text-decoration: none; }
/* run console ansi colors (based on base16-default-dark and base16-bright) */
:root {
--ansi-black: #181818;
--ansi-red: #ab4642;
--ansi-red: #f8f8f8;
--ansi-green: #a1b56c;
--ansi-yellow: #f7ca88;
--ansi-blue: #7cafc2;

View File

@ -21,16 +21,10 @@
#include "conf.h"
#include "log.h"
#include <sys/wait.h>
#include <iostream>
#include <unistd.h>
#include <signal.h>
#if defined(__FreeBSD__)
#include <sys/sysctl.h>
#include <sys/limits.h>
#endif
// short syntax helper for kj::Path
template<typename T>
inline kj::Path operator/(const kj::Path& p, const T& ext) {
@ -159,20 +153,7 @@ kj::Promise<RunState> Run::start(RunState lastResult, std::shared_ptr<Context> c
// main() by calling leader_main()
char* procName;
if(asprintf(&procName, "{laminar} %s:%d", name.data(), build) > 0)
#if defined(__FreeBSD__)
{
int sysctl_rq[] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
size_t self_exe_len = PATH_MAX;
char self_exe[PATH_MAX];
if (sysctl(sysctl_rq, 4, self_exe, &self_exe_len, NULL, 0))
_exit(EXIT_FAILURE);
execl(self_exe, procName, NULL); // does not return
}
#else
execl("/proc/self/exe", procName, NULL); // does not return
#endif
_exit(EXIT_FAILURE);
}

View File

@ -28,8 +28,9 @@
#include <signal.h>
#include <sys/eventfd.h>
#include <sys/stat.h>
#include <sys/inotify.h>
#include <sys/signalfd.h>
#include <sys/stat.h>
// Size of buffer used to read from file descriptors. Should be
// a multiple of sizeof(struct signalfd_siginfo) == 128

View File

@ -178,7 +178,7 @@ TEST_F(LaminarFixture, QueueFront) {
ioContext->waitScope.poll();
setNumExecutors(2);
ioContext->waitScope.poll();
ASSERT_GE(es->messages().size(), 5);
ASSERT_EQ(5, es->messages().size());
auto started1 = es->messages().at(3).GetObject();
EXPECT_STREQ("job_started", started1["type"].GetString());
EXPECT_STREQ("bar", started1["data"]["name"].GetString());