diff --git a/.gitignore b/.gitignore index b25c15b..240bdee 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *~ +result +*.qcow2 diff --git a/README.md b/README.md index 8d720c1..de877b4 100644 --- a/README.md +++ b/README.md @@ -104,11 +104,152 @@ python3 --version --- +## Nix-Based Setup (Alternative) + +This workshop includes a Nix flake for reproducible environments. Use this if you have Nix installed or want a pre-configured bootable image. + +### Quick Reference + +| Goal | Command | +|------|---------| +| Dev shell with all tools | `nix develop` | +| Apply tools to Ubuntu | `nix run github:numtide/system-manager -- switch --flake .` | +| Build bootable USB ISO | `nix build .#iso` | +| Build netboot files | `nix build .#netboot` | + +### Development Shell + +Get all workshop tools in your current shell without installing anything system-wide: + +```bash +cd perf-workshop +nix develop + +# Now you have: perf, strace, py-spy, bpftrace, hyperfine, valgrind, flamegraph, pyroscope +perf --version +py-spy --help +``` + +### System-Manager (Ubuntu/Debian) + +Install all workshop tools on an existing Ubuntu system using Nix: + +```bash +# Apply the configuration (installs tools via Nix) +nix run 'github:numtide/system-manager' -- switch --flake . + +# Configure perf permissions (system-manager can't do this) +sudo /etc/perf-workshop-setup.sh +``` + +This installs tools into `/nix/store` and adds them to your PATH without conflicting with apt packages. + +### Bootable USB Image + +Build a complete NixOS image with XFCE desktop, all tools pre-installed, and workshop materials: + +```bash +# Build the ISO (~4-5 GB) +nix build .#iso + +# Flash to USB (replace sdX with your device) +sudo dd if=result/iso/*.iso of=/dev/sdX bs=4M status=progress conv=fsync +``` + +**ISO Features:** +- XFCE desktop with auto-login (user: `workshop`, password: `workshop`) +- `copytoram` enabled — boots from USB, runs entirely from RAM (USB can be removed after boot) +- `kernel.perf_event_paranoid=1` pre-configured +- Workshop materials in `/home/workshop/perf-workshop` +- Desktop shortcut to open terminal in workshop directory +- SSH enabled for remote access + +**Requirements:** 8+ GB RAM recommended (the system runs from RAM) + +### Netboot over LAN + +For workshops with many participants, netboot is more efficient than flashing multiple USBs. + +```bash +# Build netboot bundle +nix build .#netboot +cd result + +# Contents: +# - bzImage (kernel) +# - initrd (initrd with full system, ~2-4 GB) +# - netboot.ipxe (iPXE boot script) +``` + +**Option 1: Pixiecore (easiest)** + +Pixiecore is an all-in-one PXE server — just point it at the files: + +```bash +nix shell nixpkgs#pixiecore + +# Serve on your LAN (requires root for DHCP proxy) +sudo pixiecore boot bzImage initrd \ + --cmdline "$(grep -oP 'imgargs.*? \K.*' netboot.ipxe)" +``` + +Participants set their BIOS to network boot and get the workshop environment automatically. + +**Option 2: dnsmasq + HTTP server** + +For more control or integration with existing infrastructure: + +```bash +# Terminal 1: Serve files over HTTP +python3 -m http.server 8080 +``` + +Configure dnsmasq (`/etc/dnsmasq.d/workshop.conf`): +```ini +interface=eth0 +dhcp-range=192.168.1.100,192.168.1.200,12h +enable-tftp +tftp-root=/path/to/result +dhcp-boot=netboot.ipxe +``` + +**Option 3: Existing PXE infrastructure** + +Copy files to your TFTP/HTTP server and configure your DHCP server to serve `netboot.ipxe`. + +### Flake Outputs Reference + +```bash +# List all outputs +nix flake show + +# Available outputs: +# - devShells.x86_64-linux.default # Development shell +# - packages.x86_64-linux.iso # Bootable ISO image +# - packages.x86_64-linux.netboot # Netboot bundle (kernel + initrd + ipxe) +# - packages.x86_64-linux.netboot-kernel +# - packages.x86_64-linux.netboot-initrd +# - packages.x86_64-linux.netboot-ipxe +# - nixosConfigurations.workshop-iso # NixOS config for ISO +# - nixosConfigurations.workshop-netboot # NixOS config for netboot +# - systemConfigs.default # system-manager config for Ubuntu +``` + +--- + ## Directory Structure ``` perf-workshop/ ├── README.md # This file +├── flake.nix # Nix flake (dev shell, ISO, netboot) +├── flake.lock # Locked dependencies +├── nix/ +│ ├── packages.nix # Shared package list +│ ├── common.nix # Common NixOS configuration +│ ├── iso.nix # ISO-specific configuration +│ ├── netboot.nix # Netboot-specific configuration +│ └── system-manager.nix # Ubuntu system-manager module ├── common/ │ └── CHEATSHEET.md # Quick reference card ├── scenario1-python-to-c/ diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..9b55a23 --- /dev/null +++ b/flake.lock @@ -0,0 +1,82 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1767767207, + "narHash": "sha256-Mj3d3PfwltLmukFal5i3fFt27L6NiKXdBezC1EBuZs4=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "5912c1772a44e31bf1c63c0390b90501e5026886", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "system-manager": "system-manager" + } + }, + "system-manager": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1767765652, + "narHash": "sha256-brTUOSGRmUCU0xJZI/aD5jUYZJm4RhqMRfkzS4veohg=", + "owner": "numtide", + "repo": "system-manager", + "rev": "85610060c48a0b9f1883d5b2525866fc6ddbbe03", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "system-manager", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..77382f1 --- /dev/null +++ b/flake.nix @@ -0,0 +1,144 @@ +{ + description = "Linux Performance Engineering Workshop"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + + system-manager = { + url = "github:numtide/system-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, system-manager, flake-utils }: + let + # Workshop source for embedding in ISO + workshopSrc = builtins.path { + path = ./.; + name = "perf-workshop"; + filter = path: type: + let + baseName = baseNameOf path; + in + # Exclude nix build artifacts and git + !(baseName == "result" || + baseName == ".git" || + baseName == "flake.lock" || + nixpkgs.lib.hasSuffix "~" baseName); + }; + in + flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + workshopPackages = import ./nix/packages.nix { inherit pkgs; }; + in + { + # Development shell with all workshop tools + devShells.default = pkgs.mkShell { + packages = workshopPackages; + shellHook = '' + echo "Linux Performance Workshop Development Environment" + echo "" + echo "Available tools: perf, strace, py-spy, hyperfine, bpftrace, ..." + echo "Run 'make' in any scenario directory to build examples" + ''; + }; + + # Package containing workshop materials + packages.workshop-materials = pkgs.runCommand "workshop-materials" {} '' + mkdir -p $out/share/perf-workshop + cp -r ${workshopSrc}/* $out/share/perf-workshop/ + ''; + } + ) // { + # System-manager configuration for Ubuntu + systemConfigs.default = system-manager.lib.makeSystemConfig { + modules = [ + ./nix/system-manager.nix + ]; + }; + + # NixOS configuration for bootable workshop ISO + nixosConfigurations.workshop-iso = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + specialArgs = { inherit workshopSrc; }; + modules = [ + "${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix" + ./nix/iso.nix + ]; + }; + + # NixOS configuration for netboot + nixosConfigurations.workshop-netboot = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + specialArgs = { inherit workshopSrc; }; + modules = [ + "${nixpkgs}/nixos/modules/installer/netboot/netboot.nix" + "${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix" + ./nix/netboot.nix + ]; + }; + + # Convenient alias for building the ISO + packages.x86_64-linux.iso = + self.nixosConfigurations.workshop-iso.config.system.build.isoImage; + + # Netboot artifacts + packages.x86_64-linux.netboot-kernel = + self.nixosConfigurations.workshop-netboot.config.system.build.kernel; + packages.x86_64-linux.netboot-initrd = + self.nixosConfigurations.workshop-netboot.config.system.build.netbootRamdisk; + packages.x86_64-linux.netboot-ipxe = + self.nixosConfigurations.workshop-netboot.config.system.build.netbootIpxeScript; + + # Bundle netboot files for easy serving + packages.x86_64-linux.netboot = let + pkgs = nixpkgs.legacyPackages.x86_64-linux; + cfg = self.nixosConfigurations.workshop-netboot.config; + kernel = "${cfg.system.build.kernel}/bzImage"; + initrd = "${cfg.system.build.netbootRamdisk}/initrd"; + in pkgs.runCommand "workshop-netboot" {} '' + mkdir -p $out + ln -s ${kernel} $out/bzImage + ln -s ${initrd} $out/initrd + cp ${cfg.system.build.netbootIpxeScript} $out/netboot.ipxe + + cat > $out/README.md << 'EOF' +# Workshop Netboot Files + +## Quick Start with Pixiecore (easiest) + +```bash +# Serve on your LAN (requires root for DHCP) +sudo pixiecore boot bzImage initrd --cmdline "$(grep -oP 'imgargs.*? \K.*' netboot.ipxe)" +``` + +## Using dnsmasq + HTTP server + +1. Start HTTP server in this directory: + ```bash + python3 -m http.server 8080 + ``` + +2. Configure dnsmasq (adjust interface/IP): + ``` + interface=eth0 + dhcp-range=192.168.1.100,192.168.1.200,12h + enable-tftp + tftp-root=/path/to/this/dir + dhcp-boot=netboot.ipxe + ``` + +3. For UEFI clients, you may need iPXE chainloading. + +## Files + +- bzImage: Linux kernel +- initrd: Initial ramdisk (contains full system) +- netboot.ipxe: iPXE boot script +EOF + ''; + }; +} diff --git a/nix/common.nix b/nix/common.nix new file mode 100644 index 0000000..1ef86e4 --- /dev/null +++ b/nix/common.nix @@ -0,0 +1,100 @@ +# Common NixOS configuration shared between ISO and netboot +{ config, lib, pkgs, workshopSrc, ... }: + +let + workshopPackages = import ./packages.nix { + inherit pkgs; + kernelPackages = config.boot.kernelPackages; + }; +in +{ + # System identification + system.stateVersion = "24.11"; + + # Use a recent stable kernel + boot.kernelPackages = pkgs.linuxPackages_6_12; + + # Set perf permissions for non-root users + boot.kernel.sysctl = { + "kernel.perf_event_paranoid" = 1; + "kernel.kptr_restrict" = 0; # Allow reading kernel symbols + }; + + # Workshop packages + environment.systemPackages = workshopPackages ++ (with pkgs; [ + # Additional GUI utilities + firefox # for viewing flamegraphs + xfce4-terminal + ]); + + # Create workshop user with auto-login + users.users.workshop = { + isNormalUser = true; + description = "Workshop User"; + extraGroups = [ "wheel" "video" "audio" "networkmanager" ]; + initialPassword = "workshop"; + home = "/home/workshop"; + }; + + # Auto-login to workshop user + services.displayManager.autoLogin = { + enable = true; + user = "workshop"; + }; + + # Allow passwordless sudo for workshop user + security.sudo.wheelNeedsPassword = false; + + # XFCE desktop environment + services.xserver.desktopManager.xfce.enable = true; + services.displayManager.defaultSession = "xfce"; + + # Embed workshop materials into home directory + system.activationScripts.workshopMaterials = '' + mkdir -p /home/workshop/perf-workshop + cp -rT ${workshopSrc} /home/workshop/perf-workshop + chmod -R u+w /home/workshop/perf-workshop + chown -R workshop:users /home/workshop/perf-workshop + ''; + + # Desktop shortcut for workshop + environment.etc."skel/Desktop/Workshop.desktop".text = '' + [Desktop Entry] + Type=Application + Name=Performance Workshop + Comment=Open terminal in workshop directory + Exec=xfce4-terminal --working-directory=/home/workshop/perf-workshop + Icon=utilities-terminal + Terminal=false + Categories=Development; + ''; + + # Copy desktop shortcut for workshop user + system.activationScripts.workshopDesktop = '' + mkdir -p /home/workshop/Desktop + cp /etc/skel/Desktop/Workshop.desktop /home/workshop/Desktop/ + chown -R workshop:users /home/workshop/Desktop + chmod +x /home/workshop/Desktop/Workshop.desktop + ''; + + # Networking + networking = { + hostName = "perf-workshop"; + networkmanager.enable = true; + }; + + # Enable SSH for remote access (useful for debugging) + services.openssh = { + enable = true; + settings = { + PermitRootLogin = "no"; + PasswordAuthentication = true; + }; + }; + + # Timezone (can be changed by user) + time.timeZone = "Asia/Kolkata"; + + # Locale + i18n.defaultLocale = "en_US.UTF-8"; +} diff --git a/nix/iso.nix b/nix/iso.nix new file mode 100644 index 0000000..49861fc --- /dev/null +++ b/nix/iso.nix @@ -0,0 +1,17 @@ +# NixOS ISO-specific configuration +{ config, lib, pkgs, workshopSrc, ... }: + +{ + imports = [ ./common.nix ]; + + # Enable copytoram - boots from USB, runs entirely from RAM + boot.kernelParams = [ "copytoram" ]; + + # ISO image customization + isoImage = { + volumeID = lib.mkForce "PERF_WORKSHOP"; + makeEfiBootable = true; + makeUsbBootable = true; + }; + image.fileName = lib.mkForce "perf-workshop-${config.system.nixos.label}-x86_64.iso"; +} diff --git a/nix/netboot.nix b/nix/netboot.nix new file mode 100644 index 0000000..36e2d1c --- /dev/null +++ b/nix/netboot.nix @@ -0,0 +1,8 @@ +# NixOS netboot-specific configuration +{ config, lib, pkgs, workshopSrc, ... }: + +{ + imports = [ ./common.nix ]; + + # Netboot runs entirely from RAM by default (initrd contains squashfs) +} diff --git a/nix/packages.nix b/nix/packages.nix new file mode 100644 index 0000000..21ef4ae --- /dev/null +++ b/nix/packages.nix @@ -0,0 +1,45 @@ +# Shared package list for the Linux Performance Workshop +{ pkgs, kernelPackages ? pkgs.linuxPackages }: + +with pkgs; [ + # Build essentials + gcc + gnumake + binutils + pkg-config + + # Performance and tracing tools + perf + strace + ltrace + htop + bpftrace + + # Python ecosystem + (python3.withPackages (ps: with ps; [ + pip + flask # for scenario7 pyroscope demo + ])) + py-spy + + # Benchmarking and debugging + hyperfine + valgrind + flamegraph + + # USDT/SDT support (provides sys/sdt.h) + libsystemtap + + # Continuous profiling + pyroscope + + # Utilities + curl + wget + git + file + which + less + vim + nano +] diff --git a/nix/system-manager.nix b/nix/system-manager.nix new file mode 100644 index 0000000..e63bdce --- /dev/null +++ b/nix/system-manager.nix @@ -0,0 +1,33 @@ +# System-manager module for Ubuntu systems +{ config, lib, pkgs, ... }: + +let + workshopPackages = import ./packages.nix { inherit pkgs; }; +in +{ + config = { + nixpkgs.hostPlatform = "x86_64-linux"; + + environment.systemPackages = workshopPackages; + + # Create a script to configure perf permissions + # (system-manager cannot set sysctl directly) + environment.etc."perf-workshop-setup.sh" = { + mode = "0755"; + text = '' + #!/bin/sh + # Run this script to enable perf for non-root users + echo "Setting kernel.perf_event_paranoid=1..." + sudo sysctl -w kernel.perf_event_paranoid=1 + + # Make it persistent + if ! grep -q "kernel.perf_event_paranoid" /etc/sysctl.conf 2>/dev/null; then + echo "kernel.perf_event_paranoid=1" | sudo tee -a /etc/sysctl.conf + echo "Added to /etc/sysctl.conf for persistence" + fi + + echo "Done! You can now use perf as a regular user." + ''; + }; + }; +}