404
Not Found
diff --git a/master/404.html b/master/404.html index a5c02041e..dda5f145a 100644 --- a/master/404.html +++ b/master/404.html @@ -1 +1 @@ -
Not Found
git clone https://github.com/kubernetes-sigs/node-feature-discovery
+cd node-feature-discovery
+
See customizing the build below for altering the container image registry, for example.
make
+
Optional, this example with Docker.
docker push <IMAGE_TAG>
+
To use your published image from the step above instead of the k8s.gcr.io/nfd/node-feature-discovery
image, edit image
attribute in the spec template(s) to the new location (<registry-name>/<image-name>[:<version>]
).
The yamls
makefile generates deployment specs matching your locally built image. See build customization below for configurability, e.g. changing the deployment namespace.
K8S_NAMESPACE=my-ns make yamls
+kubectl apply -f nfd-master.yaml
+kubectl apply -f nfd-worker-daemonset.yaml
+
Alternatively, deploying worker and master in the same pod:
K8S_NAMESPACE=my-ns make yamls
+kubectl apply -f nfd-master.yaml
+kubectl apply -f nfd-daemonset-combined.yaml
+
Or worker as a one-shot job:
K8S_NAMESPACE=my-ns make yamls
+kubectl apply -f nfd-master.yaml
+NUM_NODES=$(kubectl get no -o jsonpath='{.items[*].metadata.name}' | wc -w)
+sed s"/NUM_NODES/$NUM_NODES/" nfd-worker-job.yaml | kubectl apply -f -
+
You can also build the binaries locally
make build
+
This will compile binaries under bin/
There are several Makefile variables that control the build process and the name of the resulting container image. The following are targeted targeted for build customization and they can be specified via environment variables or makefile overrides.
Variable | Description | Default value |
---|---|---|
HOSTMOUNT_PREFIX | Prefix of system directories for feature discovery (local builds) | / (local builds) /host- (container builds) |
IMAGE_BUILD_CMD | Command to build the image | docker build |
IMAGE_BUILD_EXTRA_OPTS | Extra options to pass to build command | empty |
IMAGE_PUSH_CMD | Command to push the image to remote registry | docker push |
IMAGE_REGISTRY | Container image registry to use | k8s.gcr.io/nfd |
IMAGE_TAG_NAME | Container image tag name | <nfd version> |
IMAGE_EXTRA_TAG_NAMES | Additional container image tag(s) to create when building image | empty |
K8S_NAMESPACE | nfd-master and nfd-worker namespace | kube-system |
KUBECONFIG | Kubeconfig for running e2e-tests | empty |
E2E_TEST_CONFIG | Parameterization file of e2e-tests (see example) | empty |
For example, to use a custom registry:
make IMAGE_REGISTRY=<my custom registry uri>
+
Or to specify a build tool different from Docker, It can be done in 2 ways:
IMAGE_BUILD_CMD="buildah bud" make
+
make IMAGE_BUILD_CMD="buildah bud"
+
Unit tests are automatically run as part of the container image build. You can also run them manually in the source code tree by simply running:
make test
+
End-to-end tests are built on top of the e2e test framework of Kubernetes, and, they required a cluster to run them on. For running the tests on your test cluster you need to specify the kubeconfig to be used:
make e2e-test KUBECONFIG=$HOME/.kube/config
+
You can run NFD locally, either directly on your host OS or in containers for testing and development purposes. This may be useful e.g. for checking features-detection.
When running as a standalone container labeling is expected to fail because Kubernetes API is not available. Thus, it is recommended to use --no-publish
command line flag. E.g.
$ export NFD_CONTAINER_IMAGE=gcr.io/k8s-staging-nfd/node-feature-discovery:master
+$ docker run --rm --name=nfd-test ${NFD_CONTAINER_IMAGE} nfd-master --no-publish
+2019/02/01 14:48:21 Node Feature Discovery Master <NFD_VERSION>
+2019/02/01 14:48:21 gRPC server serving on port: 8080
+
Command line flags of nfd-master:
$ docker run --rm ${NFD_CONTAINER_IMAGE} nfd-master --help
+...
+Usage:
+ nfd-master [--prune] [--no-publish] [--label-whitelist=<pattern>] [--port=<port>]
+ [--ca-file=<path>] [--cert-file=<path>] [--key-file=<path>]
+ [--verify-node-name] [--extra-label-ns=<list>] [--resource-labels=<list>]
+ [--kubeconfig=<path>]
+ nfd-master -h | --help
+ nfd-master --version
+
+ Options:
+ -h --help Show this screen.
+ --version Output version and exit.
+ --prune Prune all NFD related attributes from all nodes
+ of the cluster and exit.
+ --kubeconfig=<path> Kubeconfig to use [Default: ]
+ --port=<port> Port on which to listen for connections.
+ [Default: 8080]
+ --ca-file=<path> Root certificate for verifying connections
+ [Default: ]
+ --cert-file=<path> Certificate used for authenticating connections
+ [Default: ]
+ --key-file=<path> Private key matching --cert-file
+ [Default: ]
+ --verify-node-name Verify worker node name against CN from the TLS
+ certificate. Only has effect when TLS authentication
+ has been enabled.
+ --no-publish Do not publish feature labels
+ --label-whitelist=<pattern> Regular expression to filter label names to
+ publish to the Kubernetes API server.
+ NB: the label namespace is omitted i.e. the filter
+ is only applied to the name part after '/'.
+ [Default: ]
+ --extra-label-ns=<list> Comma separated list of allowed extra label namespaces
+ [Default: ]
+ --resource-labels=<list> Comma separated list of labels to be exposed as extended resources.
+ [Default: ]
+
In order to run nfd-worker as a "stand-alone" container against your standalone nfd-master you need to run them in the same network namespace:
$ docker run --rm --network=container:nfd-test ${NFD_CONTAINER_IMAGE} nfd-worker
+2019/02/01 14:48:56 Node Feature Discovery Worker <NFD_VERSION>
+...
+
If you just want to try out feature discovery without connecting to nfd-master, pass the --no-publish
flag to nfd-worker.
Command line flags of nfd-worker:
$ docker run --rm ${NFD_CONTAINER_IMAGE} nfd-worker --help
+...
+nfd-worker.
+
+ Usage:
+ nfd-worker [--no-publish] [--sources=<sources>] [--label-whitelist=<pattern>]
+ [--oneshot | --sleep-interval=<seconds>] [--config=<path>]
+ [--options=<config>] [--server=<server>] [--server-name-override=<name>]
+ [--ca-file=<path>] [--cert-file=<path>] [--key-file=<path>]
+ nfd-worker -h | --help
+ nfd-worker --version
+
+ Options:
+ -h --help Show this screen.
+ --version Output version and exit.
+ --config=<path> Config file to use.
+ [Default: /etc/kubernetes/node-feature-discovery/nfd-worker.conf]
+ --options=<config> Specify config options from command line. Config
+ options are specified in the same format as in the
+ config file (i.e. json or yaml). These options
+ will override settings read from the config file.
+ [Default: ]
+ --ca-file=<path> Root certificate for verifying connections
+ [Default: ]
+ --cert-file=<path> Certificate used for authenticating connections
+ [Default: ]
+ --key-file=<path> Private key matching --cert-file
+ [Default: ]
+ --server=<server> NFD server address to connecto to.
+ [Default: localhost:8080]
+ --server-name-override=<name> Name (CN) expect from server certificate, useful
+ in testing
+ [Default: ]
+ --sources=<sources> Comma separated list of feature sources.
+ [Default: cpu,custom,iommu,kernel,local,memory,network,pci,storage,system,usb]
+ --no-publish Do not publish discovered features to the
+ cluster-local Kubernetes API server.
+ --label-whitelist=<pattern> Regular expression to filter label names to
+ publish to the Kubernetes API server.
+ NB: the label namespace is omitted i.e. the filter
+ is only applied to the name part after '/'.
+ [Default: ]
+ --oneshot Label once and exit.
+ --sleep-interval=<seconds> Time to sleep between re-labeling. Non-positive
+ value implies no re-labeling (i.e. infinite
+ sleep). [Default: 60s]
+
NOTE Some feature sources need certain directories and/or files from the host mounted inside the NFD container. Thus, you need to provide Docker with the correct --volume
options in order for them to work correctly when run stand-alone directly with docker run
. See the template spec for up-to-date information about the required volume mounts.
All documentation resides under the docs directory in the source tree. It is designed to be served as a html site by GitHub Pages.
Building the documentation is containerized in order to fix the build environment. The recommended way for developing documentation is to run:
make site-serve
+
This will build the documentation in a container and serve it under localhost:4000/ making it easy to verify the results. Any changes made to the docs/
will automatically re-trigger a rebuild and are reflected in the served content and can be inspected with a simple browser refresh.
In order to just build the html documentation run:
make site-build
+
This will generate html documentation under docs/_site/
.
Advanced topics and reference.
To quickly view available command line flags execute nfd-master --help
. In a docker container:
docker run gcr.io/k8s-staging-nfd/node-feature-discovery:master nfd-master --help
+
Print usage and exit.
Print version and exit.
The --prune
flag is a sub-command like option for cleaning up the cluster. It causes nfd-master to remove all NFD related labels, annotations and extended resources from all Node objects of the cluster and exit.
The --port
flag specifies the TCP port that nfd-master listens for incoming requests.
Default: 8080
Example:
nfd-master --port=443
+
The --ca-file
is one of the three flags (together with --cert-file
and --key-file
) controlling master-worker mutual TLS authentication on the nfd-master side. This flag specifies the TLS root certificate that is used for authenticating incoming connections. NFD-Worker side needs to have matching key and cert files configured in order for the incoming requests to be accepted.
Default: empty
Note: Must be specified together with --cert-file
and --key-file
Example:
nfd-master --ca-file=/opt/nfd/ca.crt --cert-file=/opt/nfd/master.crt --key-file=/opt/nfd/master.key
+
The --cert-file
is one of the three flags (together with --ca-file
and --key-file
) controlling master-worker mutual TLS authentication on the nfd-master side. This flag specifies the TLS certificate presented for authenticating outgoing traffic towards nfd-worker.
Default: empty
Note: Must be specified together with --ca-file
and --key-file
Example:
nfd-master --cert-file=/opt/nfd/master.crt --key-file=/opt/nfd/master.key --ca-file=/opt/nfd/ca.crt
+
The --key-file
is one of the three flags (together with --ca-file
and --cert-file
) controlling master-worker mutual TLS authentication on the nfd-master side. This flag specifies the private key corresponding the given certificate file (--cert-file
) that is used for authenticating outgoing traffic.
Default: empty
Note: Must be specified together with --cert-file
and --ca-file
Example:
nfd-master --key-file=/opt/nfd/master.key --cert-file=/opt/nfd/master.crt --ca-file=/opt/nfd/ca.crt
+
The --verify-node-name
flag controls the NodeName based authorization of incoming requests and only has effect when mTLS authentication has been enabled (with --ca-file
, --cert-file
and --key-file
). If enabled, the worker node name of the incoming must match with the CN in its TLS certificate. Thus, workers are only able to label the node they are running on (or the node whose certificate they present), and, each worker must have an individual certificate.
Node Name based authorization is disabled by default and thus it is possible for all nfd-worker pods in the cluster to use one shared certificate, making NFD deployment much easier.
Default: false
Example:
nfd-master --verify-node-name --ca-file=/opt/nfd/ca.crt \
+ --cert-file=/opt/nfd/master.crt --key-file=/opt/nfd/master.key
+
The --no-publish
flag disables all communication with the Kubernetes API server, making a "dry-run" flag for nfd-master. No Labels, Annotations or ExtendedResources (or any other properties of any Kubernetes API objects) are modified.
Default: false
Example:
nfd-master --no-publish
+
The --label-whitelist
specifies a regular expression for filtering feature labels based on their name. Each label must match against the given reqular expression in order to be published.
Note: The regular expression is only matches against the "basename" part of the label, i.e. to the part of the name after ‘/'. The label namespace is omitted.
Default: empty
Example:
nfd-master --label-whitelist='.*cpuid\.'
+
The --extra-label-ns
flag specifies a comma-separated list of allowed feature label namespaces. By default, nfd-master only allows creating labels in the default feature.node.kubernetes.io
label namespace. This option can be used to allow vendor-specific namespaces for custom labels from the local and custom feature sources.
The same namespace control and this flag applies Extended Resources (created with --resource-labels
), too.
Default: empty
Example:
nfd-master --extra-label-ns=vendor-1.com,vendor-2.io
+
The --resource-labels
flag specifies a comma-separated list of features to be advertised as extended resources instead of labels. Features that have integer values can be published as Extended Resources by listing them in this flag.
Default: empty
Example:
nfd-master --resource-labels=vendor-1.com/feature-1,vendor-2.io/feature-2
+
To quickly view available command line flags execute nfd-worker --help
. In a docker container:
docker run gcr.io/k8s-staging-nfd/node-feature-discovery:master nfd-worker --help
+
Print usage and exit.
Print version and exit.
The --config
flag specifies the path of the nfd-worker configuration file to use.
Default: /etc/kubernetes/node-feature-discovery/nfd-worker.conf
Example:
nfd-worker --config=/opt/nfd/worker.conf
+
The --options
flag may be used to specify and override configuration file options directly from the command line. The required format is the same as in the config file i.e. JSON or YAML. Configuration options specified via this flag will override those from the configuration file:
Default: empty
Example:
nfd-worker --options='{"sources":{"cpu":{"cpuid":{"attributeWhitelist":["AVX","AVX2"]}}}}'
+
The --server
flag specifies the address of the nfd-master endpoint where to connect to.
Default: localhost:8080
Example:
nfd-worker --server=nfd-master.nfd.svc.cluster.local:443
+
The --ca-file
is one of the three flags (together with --cert-file
and --key-file
) controlling the mutual TLS authentication on the worker side. This flag specifies the TLS root certificate that is used for verifying the authenticity of nfd-master.
Default: empty
Note: Must be specified together with --cert-file
and --key-file
Example:
nfd-worker --ca-file=/opt/nfd/ca.crt --cert-file=/opt/nfd/worker.crt --key-file=/opt/nfd/worker.key
+
The --cert-file
is one of the three flags (together with --ca-file
and --key-file
) controlling mutual TLS authentication on the worker side. This flag specifies the TLS certificate presented for authenticating outgoing requests.
Default: empty
Note: Must be specified together with --ca-file
and --key-file
Example:
nfd-workerr --cert-file=/opt/nfd/worker.crt --key-file=/opt/nfd/worker.key --ca-file=/opt/nfd/ca.crt
+
The --key-file
is one of the three flags (together with --ca-file
and --cert-file
) controlling the mutual TLS authentication on the worker side. This flag specifies the private key corresponding the given certificate file (--cert-file
) that is used for authenticating outgoing requests.
Default: empty
Note: Must be specified together with --cert-file
and --ca-file
Example:
nfd-worker --key-file=/opt/nfd/worker.key --cert-file=/opt/nfd/worker.crt --ca-file=/opt/nfd/ca.crt
+
The --server-name-override
flag specifies the common name (CN) which to expect from the nfd-master TLS certificate. This flag is mostly intended for development and debugging purposes.
Default: empty
Example:
nfd-worker --server-name-override=localhost
+
The --sources
flag specifies a comma-separated list of enabled feature sources.
Default: cpu,custom,iommu,kernel,local,memory,network,pci,storage,system,usb
Example:
nfd-worker --sources=kernel,system,local
+
The --no-publish
flag disables all communication with the nfd-master, making it a "dry-run" flag for nfd-worker. NFD-Worker runs feature detection normally, but no labeling requests are sent to nfd-master.
Default: false
Example:
nfd-worker --no-publish
+
The --label-whitelist
specifies a regular expression for filtering feature labels based on their name. Each label must match against the given reqular expression in order to be published.
Note: The regular expression is only matches against the "basename" part of the label, i.e. to the part of the name after ‘/'. The label namespace is omitted.
Default: empty
Example:
nfd-worker --label-whitelist='.*cpuid\.'
+
The --oneshot
flag causes nfd-worker to exit after one pass of feature detection.
Default: false
Example:
nfd-worker --oneshot --no-publish
+
The --sleep-interval
specifies the interval between feature re-detection (and node re-labeling). A non-positive value implies infinite sleep interval, i.e. no re-detection or re-labeling is done.
Default: 60s
Example:
nfd-worker --sleep-interval=1h
+
>1)+f+t+w+C.slice(T);break;default:t=C+f+t+w}return s(t)}return y=void 0===y?6:/[gprs]/.test(m)?Math.max(1,Math.min(21,y)):Math.max(0,Math.min(20,y)),w.toString=function(){return t+""},w}return{format:h,formatPrefix:function(t,e){var n=h(((t=Vs(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor($s(e)/3))),i=Math.pow(10,-r),a=ec[8+r/3];return function(t){return n(i*t)+a}}}};function rc(t){return qs=nc(t),Xs=qs.format,Zs=qs.formatPrefix,qs}rc({decimal:".",thousands:",",grouping:[3],currency:["$",""],minus:"-"});var ic=function(t){return Math.max(0,-$s(Math.abs(t)))},ac=function(t,e){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor($s(e)/3)))-$s(Math.abs(t)))},oc=function(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,$s(e)-$s(t))+1},sc=function(){return new cc};function cc(){this.reset()}cc.prototype={constructor:cc,reset:function(){this.s=this.t=0},add:function(t){lc(uc,t,this.t),lc(this,uc.s,this.s),this.s?this.t+=uc.t:this.s=uc.t},valueOf:function(){return this.s}};var uc=new cc;function lc(t,e,n){var r=t.s=e+n,i=r-e,a=r-i;t.t=e-a+(n-i)}var hc=Math.PI,fc=hc/2,dc=hc/4,pc=2*hc,gc=180/hc,yc=hc/180,vc=Math.abs,mc=Math.atan,bc=Math.atan2,xc=Math.cos,_c=Math.ceil,kc=Math.exp,wc=(Math.floor,Math.log),Ec=Math.pow,Tc=Math.sin,Cc=Math.sign||function(t){return t>0?1:t<0?-1:0},Ac=Math.sqrt,Sc=Math.tan;function Mc(t){return t>1?0:t<-1?hc:Math.acos(t)}function Oc(t){return t>1?fc:t<-1?-fc:Math.asin(t)}function Dc(t){return(t=Tc(t/2))*t}function Nc(){}function Bc(t,e){t&&Fc.hasOwnProperty(t.type)&&Fc[t.type](t,e)}var Lc={Feature:function(t,e){Bc(t.geometry,e)},FeatureCollection:function(t,e){for(var n=t.features,r=-1,i=n.length;++r=0?1:-1,i=r*n,a=xc(e=(e*=yc)/2+dc),o=Tc(e),s=Uc*o,c=zc*a+s*xc(i),u=s*r*Tc(i);Wc.add(bc(u,c)),Yc=t,zc=a,Uc=o}var Jc=function(t){return Vc.reset(),$c(t,Hc),2*Vc};function Qc(t){return[bc(t[1],t[0]),Oc(t[2])]}function Kc(t){var e=t[0],n=t[1],r=xc(n);return[r*xc(e),r*Tc(e),Tc(n)]}function tu(t,e){return t[0]*e[0]+t[1]*e[1]+t[2]*e[2]}function eu(t,e){return[t[1]*e[2]-t[2]*e[1],t[2]*e[0]-t[0]*e[2],t[0]*e[1]-t[1]*e[0]]}function nu(t,e){t[0]+=e[0],t[1]+=e[1],t[2]+=e[2]}function ru(t,e){return[t[0]*e,t[1]*e,t[2]*e]}function iu(t){var e=Ac(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=e,t[1]/=e,t[2]/=e}var au,ou,su,cu,uu,lu,hu,fu,du,pu,gu=sc(),yu={point:vu,lineStart:bu,lineEnd:xu,polygonStart:function(){yu.point=_u,yu.lineStart=ku,yu.lineEnd=wu,gu.reset(),Hc.polygonStart()},polygonEnd:function(){Hc.polygonEnd(),yu.point=vu,yu.lineStart=bu,yu.lineEnd=xu,Wc<0?(au=-(su=180),ou=-(cu=90)):gu>1e-6?cu=90:gu<-1e-6&&(ou=-90),pu[0]=au,pu[1]=su},sphere:function(){au=-(su=180),ou=-(cu=90)}};function vu(t,e){du.push(pu=[au=t,su=t]),e >>1;u[g] >1)+f+t+w+C.slice(T);break;default:t=C+f+t+w}return s(t)}return y=void 0===y?6:/[gprs]/.test(m)?Math.max(1,Math.min(21,y)):Math.max(0,Math.min(20,y)),w.toString=function(){return t+""},w}return{format:h,formatPrefix:function(t,e){var n=h(((t=Vs(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor($s(e)/3))),i=Math.pow(10,-r),a=ec[8+r/3];return function(t){return n(i*t)+a}}}};function rc(t){return qs=nc(t),Xs=qs.format,Zs=qs.formatPrefix,qs}rc({decimal:".",thousands:",",grouping:[3],currency:["$",""],minus:"-"});var ic=function(t){return Math.max(0,-$s(Math.abs(t)))},ac=function(t,e){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor($s(e)/3)))-$s(Math.abs(t)))},oc=function(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,$s(e)-$s(t))+1},sc=function(){return new cc};function cc(){this.reset()}cc.prototype={constructor:cc,reset:function(){this.s=this.t=0},add:function(t){lc(uc,t,this.t),lc(this,uc.s,this.s),this.s?this.t+=uc.t:this.s=uc.t},valueOf:function(){return this.s}};var uc=new cc;function lc(t,e,n){var r=t.s=e+n,i=r-e,a=r-i;t.t=e-a+(n-i)}var hc=Math.PI,fc=hc/2,dc=hc/4,pc=2*hc,gc=180/hc,yc=hc/180,vc=Math.abs,mc=Math.atan,bc=Math.atan2,xc=Math.cos,_c=Math.ceil,kc=Math.exp,wc=(Math.floor,Math.log),Ec=Math.pow,Tc=Math.sin,Cc=Math.sign||function(t){return t>0?1:t<0?-1:0},Sc=Math.sqrt,Ac=Math.tan;function Mc(t){return t>1?0:t<-1?hc:Math.acos(t)}function Oc(t){return t>1?fc:t<-1?-fc:Math.asin(t)}function Dc(t){return(t=Tc(t/2))*t}function Nc(){}function Bc(t,e){t&&Fc.hasOwnProperty(t.type)&&Fc[t.type](t,e)}var Lc={Feature:function(t,e){Bc(t.geometry,e)},FeatureCollection:function(t,e){for(var n=t.features,r=-1,i=n.length;++r=0?1:-1,i=r*n,a=xc(e=(e*=yc)/2+dc),o=Tc(e),s=Uc*o,c=zc*a+s*xc(i),u=s*r*Tc(i);Wc.add(bc(u,c)),Yc=t,zc=a,Uc=o}var Jc=function(t){return Vc.reset(),$c(t,Hc),2*Vc};function Qc(t){return[bc(t[1],t[0]),Oc(t[2])]}function Kc(t){var e=t[0],n=t[1],r=xc(n);return[r*xc(e),r*Tc(e),Tc(n)]}function tu(t,e){return t[0]*e[0]+t[1]*e[1]+t[2]*e[2]}function eu(t,e){return[t[1]*e[2]-t[2]*e[1],t[2]*e[0]-t[0]*e[2],t[0]*e[1]-t[1]*e[0]]}function nu(t,e){t[0]+=e[0],t[1]+=e[1],t[2]+=e[2]}function ru(t,e){return[t[0]*e,t[1]*e,t[2]*e]}function iu(t){var e=Sc(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=e,t[1]/=e,t[2]/=e}var au,ou,su,cu,uu,lu,hu,fu,du,pu,gu=sc(),yu={point:vu,lineStart:bu,lineEnd:xu,polygonStart:function(){yu.point=_u,yu.lineStart=ku,yu.lineEnd=wu,gu.reset(),Hc.polygonStart()},polygonEnd:function(){Hc.polygonEnd(),yu.point=vu,yu.lineStart=bu,yu.lineEnd=xu,Wc<0?(au=-(su=180),ou=-(cu=90)):gu>1e-6?cu=90:gu<-1e-6&&(ou=-90),pu[0]=au,pu[1]=su},sphere:function(){au=-(su=180),ou=-(cu=90)}};function vu(t,e){du.push(pu=[au=t,su=t]),ecu&&(cu=e)),u?t0?r=A(s=Math.floor(s/r)*r,c=Math.ceil(c/r)*r,n):r<0&&(r=A(s=Math.ceil(s*r)/r,c=Math.floor(c*r)/r,n)),r>0?(i[a]=Math.floor(s/r)*r,i[o]=Math.ceil(c/r)*r,e(i)):r<0&&(i[a]=Math.ceil(s*r)/r,i[o]=Math.floor(c*r)/r,e(i)),t},t}function sg(){var t=ig(Jp,Jp);return t.copy=function(){return ng(t,sg())},Rp.apply(t,arguments),og(t)}function cg(t){var e;function n(t){return isNaN(t=+t)?e:t}return n.invert=n,n.domain=n.range=function(e){return arguments.length?(t=Up.call(e,Xp),n):t.slice()},n.unknown=function(t){return arguments.length?(e=t,n):e},n.copy=function(){return cg(t).unknown(e)},t=arguments.length?Up.call(t,Xp):[0,1],og(n)}var ug=function(t,e){var n,r=0,i=(t=t.slice()).length-1,a=t[r],o=t[i];return o0){for(;fc)break;g.push(h)}}else g=C(f,d,Math.min(d-f,p)).map(n);return r?g.reverse():g},r.tickFormat=function(t,i){if(null==i&&(i=10===a?".0e":","),"function"!=typeof i&&(i=Xs(i)),t===1/0)return i;null==t&&(t=10);var o=Math.max(1,a*t/r.ticks().length);return function(t){var r=t/n(Math.round(e(t)));return r*a=c)return-1;if(37===(i=e.charCodeAt(o++))){if(i=e.charAt(o++),!(a=_[i in Hy?e.charAt(o++):i])||(r=a(t,n,r))<0)return-1}else if(i!=n.charCodeAt(r++))return-1}return r}return(b.x=k(n,b),b.X=k(r,b),b.c=k(e,b),x.x=k(n,x),x.X=k(r,x),x.c=k(e,x),{format:function(t){var e=k(t+="",b);return e.toString=function(){return t},e},parse:function(t){var e=w(t+="",!1);return e.toString=function(){return t},e},utcFormat:function(t){var e=k(t+="",x);return e.toString=function(){return t},e},utcParse:function(t){var e=w(t+="",!0);return e.toString=function(){return t},e}})}var zy,Uy,$y,Wy,Vy,Hy={"-":"",_:" ",0:"0"},Gy=/^\s*\d+/,qy=/^%/,Xy=/[\\^$*+?|[\]().{}]/g;function Zy(t,e,n){var r=t<0?"-":"",i=(r?-t:t)+"",a=i.length;return r+(ah&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},M={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;acu&&(cu=e)),u?t