Skip to content

Commit

Permalink
feat(injector): zig-based injector
Browse files Browse the repository at this point in the history
  • Loading branch information
mmanciop committed Jan 29, 2025
1 parent 7303e3d commit 6853d0e
Show file tree
Hide file tree
Showing 26 changed files with 508 additions and 761 deletions.
55 changes: 30 additions & 25 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ concurrency:
jobs:
verify:
name: Build & Test
runs-on: ubuntu-latest
runs-on: 8core_32gb
timeout-minutes: 15

steps:
Expand Down Expand Up @@ -76,7 +76,15 @@ jobs:
injector_binary_and_instrumentation_image_tests:
name: Injector Binary & Instrumentation Image Tests
runs-on: ubuntu-latest
continue-on-error: true
strategy:
fail-fast: false
matrix:
# We run the job on separate architectures to avoid using emulation in Docker
# for transpiling (which is somehow broken in Zig with QEmu below 9, see
# https://github.com/ziglang/zig/issues/22643), and SLOW.
runner: ["8core_32gb", "8core_32gb_arm64"]
runs-on: ${{ matrix.runner }}
timeout-minutes: 40
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -110,6 +118,16 @@ jobs:
- .github/workflows/ci.yaml
- images/instrumentation/**
- name: builder arch to docker
id: docker-arch
run: |
if [ $(arch) = 'aarch64' ]
then
echo 'docker_arch=linux/arm64' >> $GITHUB_OUTPUT
else
echo 'docker_arch=linux/amd64' >> $GITHUB_OUTPUT
fi
- name: show changed files
env:
INSTRUMENTATION_CHANGED_FILES_FLAG: ${{ steps.changed-files.outputs.instrumentation_any_changed }}
Expand All @@ -124,14 +142,6 @@ jobs:
steps.changed-files.outputs.instrumentation_any_changed == 'true' ||
github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/')
# Just for building on arm, buildx is enough but doing docker run with --platform=linux/arm64 (which we do when
# testing the injector binary and the instrumentation image) requires qemu.
- name: set up qemu
uses: docker/setup-qemu-action@v3
if: |
steps.changed-files.outputs.instrumentation_any_changed == 'true' ||
github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/')
- name: login to GitHub container registry
uses: docker/login-action@v3
if: |
Expand All @@ -153,6 +163,7 @@ jobs:
echo "image_name=ghcr.io/dash0hq/instrumentation-ci-test:$image_tag" >> $GITHUB_OUTPUT
- name: build temporary instrumentation image
id: build-test-instrumentation-image
uses: docker/build-push-action@v6
# Dependabot PRs currently fail in this step, see
# https://stackoverflow.com/questions/74788092/github-dependabot-doesnt-have-permissions-to-publish-to-ghcr-how-can-i-give-it
Expand All @@ -169,26 +180,19 @@ jobs:
with:
context: images/instrumentation
tags: ${{ steps.instrumentation-test-image.outputs.image_name }}
platforms: linux/amd64,linux/arm64
platforms: ${{ steps.docker-arch.outputs.docker_arch }}
cache-from: type=gha,scope=instrumentation
cache-to: type=gha,mode=max,scope=instrumentation
push: true

- name: injector tests
if: |
steps.changed-files.outputs.instrumentation_any_changed == 'true' ||
github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/')
env:
INSTRUMENTATION_IMAGE: ${{ steps.instrumentation-test-image.outputs.image_name }}
run: |
images/instrumentation/injector/test/scripts/test-all.sh
- name: instrumentation image tests
if: |
steps.changed-files.outputs.instrumentation_any_changed == 'true' ||
github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/')
env:
INSTRUMENTATION_IMAGE: ${{ steps.instrumentation-test-image.outputs.image_name }}
INSTRUMENTATION_IMAGE: ${{ steps.instrumentation-test-image.outputs.image_name }}@${{ steps.build-test-instrumentation-image.outputs.imageid }}
IS_REMOTE_IMAGE: false
ARCHITECTURES: ${{ steps.docker-arch.outputs.docker_arch }}
run: |
images/instrumentation/test/test-all.sh
Expand All @@ -210,7 +214,7 @@ jobs:
# meant for production use.
build-and-push-images:
name: Build Images
runs-on: ubuntu-latest
runs-on: 8core_32gb_arm64
needs:
- verify
- injector_binary_and_instrumentation_image_tests
Expand Down Expand Up @@ -275,7 +279,7 @@ jobs:

publish-helm-chart-dry-run:
name: Publish Helm Chart (Dry Run)
runs-on: ubuntu-latest
runs-on: 8core_32gb_arm64
if: ${{ ! contains(github.ref, 'refs/tags/') && github.actor != 'dependabot[bot]'}}
needs:
- build-and-push-images
Expand All @@ -297,7 +301,8 @@ jobs:
# branch protection rules reference this property, and it is a required check.
skip-publish-helm-chart-dry-run-for-dependabot:
name: Publish Helm Chart (Dry Run)
runs-on: ubuntu-latest
runs-on: 8core_32gb_arm64

if: ${{ ! contains(github.ref, 'refs/tags/') && github.actor == 'dependabot[bot]'}}
needs:
- build-and-push-images
Expand All @@ -309,7 +314,7 @@ jobs:
publish-helm-chart:
name: Publish Helm Chart
runs-on: ubuntu-latest
runs-on: 8core_32gb_arm64
if: ${{ contains(github.ref, 'refs/tags/') && github.actor != 'dependabot[bot]'}}
needs:
- build-and-push-images
Expand Down
21 changes: 7 additions & 14 deletions images/instrumentation/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
# injector_build_start - do not remove this line (see images/instrumentation/injector/test/scripts/test-all.sh)

# build injector
FROM ubuntu:24.04 AS build-injector
FROM alpine:3.21 AS build-injector

RUN apt-get update && \
apt-get install --no-install-recommends build-essential -y && \
apt-get autoremove -y && \
apt-get clean -y
## Zig is available as a community package on Alpine
RUN apk add zig --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community

COPY ./injector /dash0-init-container
ARG TARGETARCH
WORKDIR /dash0-init-container
RUN gcc \
-shared \
-nostdlib \
-fPIC \
-Wl,--version-script=src/dash0_injector.exports.map \
src/dash0_injector.c \
-o dash0_injector.so
RUN zig build -Dcpu-arch=${TARGETARCH} --summary all

# injector_build_end - do not remove this line (see images/instrumentation/injector/test/scripts/test-all.sh)

Expand All @@ -42,12 +35,12 @@ RUN NPM_CONFIG_UPDATE_NOTIFIER=false \
--no-fund=true

# build final image
FROM alpine:3.19.1
FROM alpine:3.21
COPY copy-instrumentation.sh /

# copy artifacts (distros, injector binary) from the build stages to the final image
RUN mkdir -p /dash0-init-container/instrumentation
COPY --from=build-injector /dash0-init-container/dash0_injector.so /dash0-init-container/dash0_injector.so
COPY --from=build-injector /dash0-init-container/bin /dash0-init-container
COPY --from=build-jvm /dash0-init-container/instrumentation/jvm/build /dash0-init-container/instrumentation/jvm
COPY --from=build-node.js /dash0-init-container/instrumentation/node.js /dash0-init-container/instrumentation/node.js

Expand Down
2 changes: 2 additions & 0 deletions images/instrumentation/injector/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.zig-cache
zig-out
5 changes: 5 additions & 0 deletions images/instrumentation/injector/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@




https://richiejp.com/zig-ld-preload-trick
30 changes: 1 addition & 29 deletions images/instrumentation/injector/bin/NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,4 @@ Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.




This codebase contains code from musl libc (https://musl.libc.org)
subject to the following license terms:

----------------------------------------------------------------------
Copyright © 2005-2014 Rich Felker, et al.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
limitations under the License.
60 changes: 60 additions & 0 deletions images/instrumentation/injector/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const std = @import("std");

pub const InjectorBuildError = error{UnsupportedArchitecturError};

// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.

pub fn build(b: *std.Build) !void {
const optimize = b.standardOptimizeOption(.{});

var targetCpuArch = std.Target.Cpu.Arch.aarch64;
var targetCpuModel = std.Target.Cpu.Model.generic(std.Target.Cpu.Arch.aarch64);

if (b.option([]const u8, "cpu-arch", "The system architecture to compile the injector for; valid options are 'amd64' and 'arch64' (default)")) |val| {
if (std.mem.eql(u8, "arm64", val)) {
// Nothing to do
} else if (std.mem.eql(u8, "amd64", val)) {
targetCpuArch = std.Target.Cpu.Arch.x86_64;
targetCpuModel = std.Target.Cpu.Model.generic(std.Target.Cpu.Arch.x86_64);
} else {
return error.UnsupportedArchitecturError;
}
}

const target = b.resolveTargetQuery(.{
.cpu_arch = targetCpuArch,
// Skip cpu model detection because the automatic detection for transpiling fails in build
.cpu_model = .{ .explicit = targetCpuModel },
.os_tag = .linux,
});

const lib = b.addSharedLibrary(.{
.name = "injector",
// In this case the main source file is merely a path, however, in more
// complicated build scripts, this could be a generated file.
.root_source_file = b.path("src/dash0_injector.zig"),
.target = target,
.optimize = optimize,
.link_libc = false,
.pic = true,
.strip = false,
});
lib.setVersionScript(b.path("src/dash0_injector.exports.map"));

b.getInstallStep().dependOn(&b.addInstallArtifact(lib, .{ .dest_dir = .{ .override = .{ .custom = "." } } }).step);

var copy_injector_to_bin = b.step("copy_file", "Copy injector file");
copy_injector_to_bin.makeFn = copyInjectorFile;
copy_injector_to_bin.dependOn(b.getInstallStep());

b.default_step = copy_injector_to_bin;
}

fn copyInjectorFile(step: *std.Build.Step, _: std.Progress.Node) anyerror!void {
const source_path = step.owner.pathFromRoot("./zig-out/libinjector.so");
const dest_path = step.owner.pathFromRoot("./bin/dash0_injector.so");

try std.fs.copyFileAbsolute(source_path, dest_path, .{});
}
72 changes: 72 additions & 0 deletions images/instrumentation/injector/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
.{
// This is the default name used by packages depending on this one. For
// example, when a user runs `zig fetch --save <url>`, this field is used
// as the key in the `dependencies` table. Although the user can choose a
// different name, most users will stick with this provided value.
//
// It is redundant to include "zig" in this name because it is already
// within the Zig package namespace.
.name = "injector",

// This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication.
.version = "0.0.0",

// This field is optional.
// This is currently advisory only; Zig does not yet do anything
// with this value.
//.minimum_zig_version = "0.11.0",

// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
// Once all dependencies are fetched, `zig build` no longer requires
// internet connectivity.
.dependencies = .{
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
//.example = .{
// // When updating this field to a new URL, be sure to delete the corresponding
// // `hash`, otherwise you are communicating that you expect to find the old hash at
// // the new URL.
// .url = "https://example.com/foo.tar.gz",
//
// // This is computed from the file contents of the directory of files that is
// // obtained after fetching `url` and applying the inclusion rules given by
// // `paths`.
// //
// // This field is the source of truth; packages do not come from a `url`; they
// // come from a `hash`. `url` is just one of many possible mirrors for how to
// // obtain a package matching this `hash`.
// //
// // Uses the [multihash](https://multiformats.io/multihash/) format.
// .hash = "...",
//
// // When this is provided, the package is found in a directory relative to the
// // build root. In this case the package's hash is irrelevant and therefore not
// // computed. This field and `url` are mutually exclusive.
// .path = "foo",

// // When this is set to `true`, a package is declared to be lazily
// // fetched. This makes the dependency only get fetched if it is
// // actually used.
// .lazy = false,
//},
},

// Specifies the set of files and directories that are included in this package.
// Only files and directories listed here are included in the `hash` that
// is computed for this package. Only files listed here will remain on disk
// when using the zig package manager. As a rule of thumb, one should list
// files required for compilation plus any license(s).
// Paths are relative to the build root. Use the empty string (`""`) to refer to
// the build root itself.
// A directory listed here means that all files within, recursively, are included.
.paths = .{
"build.zig",
"build.zig.zon",
"src",
// For example...
//"LICENSE",
//"README.md",
},
}
Loading

0 comments on commit 6853d0e

Please sign in to comment.