refactor(server): Expose layer grouping logic via a function
Refactors the layer grouping package (which previously compiled to a separate binary) to expose the layer grouping logic via a function instead. This is the next step towards creating layers inside of the server component instead of in Nix. Relates to #50.
This commit is contained in:
parent
8c79d085ae
commit
9c3c622403
1 changed files with 24 additions and 55 deletions
|
@ -1,6 +1,6 @@
|
||||||
// This program reads an export reference graph (i.e. a graph representing the
|
// This package reads an export reference graph (i.e. a graph representing the
|
||||||
// runtime dependencies of a set of derivations) created by Nix and groups them
|
// runtime dependencies of a set of derivations) created by Nix and groups it in
|
||||||
// in a way that is likely to match the grouping for other derivation sets with
|
// a way that is likely to match the grouping for other derivation sets with
|
||||||
// overlapping dependencies.
|
// overlapping dependencies.
|
||||||
//
|
//
|
||||||
// This is used to determine which derivations to include in which layers of a
|
// This is used to determine which derivations to include in which layers of a
|
||||||
|
@ -9,8 +9,8 @@
|
||||||
// # Inputs
|
// # Inputs
|
||||||
//
|
//
|
||||||
// * a graph of Nix runtime dependencies, generated via exportReferenceGraph
|
// * a graph of Nix runtime dependencies, generated via exportReferenceGraph
|
||||||
// * a file containing absolute popularity values of packages in the
|
// * popularity values of each package in the Nix package set (in the form of a
|
||||||
// Nix package set (in the form of a direct reference count)
|
// direct reference count)
|
||||||
// * a maximum number of layers to allocate for the image (the "layer budget")
|
// * a maximum number of layers to allocate for the image (the "layer budget")
|
||||||
//
|
//
|
||||||
// # Algorithm
|
// # Algorithm
|
||||||
|
@ -103,9 +103,6 @@
|
||||||
package layers
|
package layers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -114,9 +111,11 @@ import (
|
||||||
"gonum.org/v1/gonum/graph/simple"
|
"gonum.org/v1/gonum/graph/simple"
|
||||||
)
|
)
|
||||||
|
|
||||||
// closureGraph represents the structured attributes Nix outputs when asking it
|
// RuntimeGraph represents structured information from Nix about the runtime
|
||||||
// for the exportReferencesGraph of a list of derivations.
|
// dependencies of a derivation.
|
||||||
type exportReferences struct {
|
//
|
||||||
|
// This is generated in Nix by using the exportReferencesGraph feature.
|
||||||
|
type RuntimeGraph struct {
|
||||||
References struct {
|
References struct {
|
||||||
Graph []string `json:"graph"`
|
Graph []string `json:"graph"`
|
||||||
} `json:"exportReferencesGraph"`
|
} `json:"exportReferencesGraph"`
|
||||||
|
@ -135,14 +134,14 @@ type exportReferences struct {
|
||||||
// of the nixpkgs tree.
|
// of the nixpkgs tree.
|
||||||
type Popularity = map[string]int
|
type Popularity = map[string]int
|
||||||
|
|
||||||
// layer represents the data returned for each layer that Nix should
|
// Layer represents the data returned for each layer that Nix should
|
||||||
// build for the container image.
|
// build for the container image.
|
||||||
type layer struct {
|
type Layer struct {
|
||||||
Contents []string `json:"contents"`
|
Contents []string `json:"contents"`
|
||||||
mergeRating uint64
|
mergeRating uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a layer) merge(b layer) layer {
|
func (a Layer) merge(b Layer) Layer {
|
||||||
a.Contents = append(a.Contents, b.Contents...)
|
a.Contents = append(a.Contents, b.Contents...)
|
||||||
a.mergeRating += b.mergeRating
|
a.mergeRating += b.mergeRating
|
||||||
return a
|
return a
|
||||||
|
@ -209,7 +208,7 @@ func insertEdges(graph *simple.DirectedGraph, cmap *map[string]*closure, node *c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a graph structure from the references supplied by Nix.
|
// Create a graph structure from the references supplied by Nix.
|
||||||
func buildGraph(refs *exportReferences, pop *Popularity) *simple.DirectedGraph {
|
func buildGraph(refs *RuntimeGraph, pop *Popularity) *simple.DirectedGraph {
|
||||||
cmap := make(map[string]*closure)
|
cmap := make(map[string]*closure)
|
||||||
graph := simple.NewDirectedGraph()
|
graph := simple.NewDirectedGraph()
|
||||||
|
|
||||||
|
@ -259,7 +258,7 @@ func buildGraph(refs *exportReferences, pop *Popularity) *simple.DirectedGraph {
|
||||||
// Extracts a subgraph starting at the specified root from the
|
// Extracts a subgraph starting at the specified root from the
|
||||||
// dominator tree. The subgraph is converted into a flat list of
|
// dominator tree. The subgraph is converted into a flat list of
|
||||||
// layers, each containing the store paths and merge rating.
|
// layers, each containing the store paths and merge rating.
|
||||||
func groupLayer(dt *flow.DominatorTree, root *closure) layer {
|
func groupLayer(dt *flow.DominatorTree, root *closure) Layer {
|
||||||
size := root.Size
|
size := root.Size
|
||||||
contents := []string{root.Path}
|
contents := []string{root.Path}
|
||||||
children := dt.DominatedBy(root.ID())
|
children := dt.DominatedBy(root.ID())
|
||||||
|
@ -273,7 +272,7 @@ func groupLayer(dt *flow.DominatorTree, root *closure) layer {
|
||||||
children = append(children, dt.DominatedBy(child.ID())...)
|
children = append(children, dt.DominatedBy(child.ID())...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return layer{
|
return Layer{
|
||||||
Contents: contents,
|
Contents: contents,
|
||||||
// TODO(tazjin): The point of this is to factor in
|
// TODO(tazjin): The point of this is to factor in
|
||||||
// both the size and the popularity when making merge
|
// both the size and the popularity when making merge
|
||||||
|
@ -288,10 +287,10 @@ func groupLayer(dt *flow.DominatorTree, root *closure) layer {
|
||||||
//
|
//
|
||||||
// Layers are merged together until they fit into the layer budget,
|
// Layers are merged together until they fit into the layer budget,
|
||||||
// based on their merge rating.
|
// based on their merge rating.
|
||||||
func dominate(budget int, graph *simple.DirectedGraph) []layer {
|
func dominate(budget int, graph *simple.DirectedGraph) []Layer {
|
||||||
dt := flow.Dominators(graph.Node(0), graph)
|
dt := flow.Dominators(graph.Node(0), graph)
|
||||||
|
|
||||||
var layers []layer
|
var layers []Layer
|
||||||
for _, n := range dt.DominatedBy(dt.Root().ID()) {
|
for _, n := range dt.DominatedBy(dt.Root().ID()) {
|
||||||
layers = append(layers, groupLayer(&dt, n.(*closure)))
|
layers = append(layers, groupLayer(&dt, n.(*closure)))
|
||||||
}
|
}
|
||||||
|
@ -313,40 +312,10 @@ func dominate(budget int, graph *simple.DirectedGraph) []layer {
|
||||||
return layers
|
return layers
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
// GroupLayers applies the algorithm described above the its input and returns a
|
||||||
graphFile := flag.String("graph", ".attrs.json", "Input file containing graph")
|
// list of layers, each consisting of a list of Nix store paths that it should
|
||||||
popFile := flag.String("pop", "popularity.json", "Package popularity data")
|
// contain.
|
||||||
outFile := flag.String("out", "layers.json", "File to write layers to")
|
func GroupLayers(refs *RuntimeGraph, pop *Popularity, budget int) []Layer {
|
||||||
layerBudget := flag.Int("budget", 94, "Total layer budget available")
|
graph := buildGraph(refs, pop)
|
||||||
flag.Parse()
|
return dominate(budget, graph)
|
||||||
|
|
||||||
// Parse graph data
|
|
||||||
file, err := ioutil.ReadFile(*graphFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to load input: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var refs exportReferences
|
|
||||||
err = json.Unmarshal(file, &refs)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to deserialise input: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse popularity data
|
|
||||||
popBytes, err := ioutil.ReadFile(*popFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to load input: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pop Popularity
|
|
||||||
err = json.Unmarshal(popBytes, &pop)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to deserialise input: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
graph := buildGraph(&refs, &pop)
|
|
||||||
layers := dominate(*layerBudget, graph)
|
|
||||||
|
|
||||||
j, _ := json.Marshal(layers)
|
|
||||||
ioutil.WriteFile(*outFile, j, 0644)
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue