// GetMetricTypes returns list of available metric types // It returns error in case retrieval was not successful func (c *collector) GetMetricTypes(cfg plugin.ConfigType) ([]plugin.MetricType, error) { mts := []plugin.MetricType{} var err error c.allTenants, err = getTenants(cfg) if err != nil { return nil, err } // Generate available namespace for limits namespaces := []string{} for _, tenantName := range c.allTenants { // Construct temporary struct to generate namespace based on tags var metrics struct { S types.Snapshots `json:"snapshots"` V types.Volumes `json:"volumes"` L types.Limits `json:"limits"` } current := strings.Join([]string{vendor, fs, name, tenantName}, "/") ns.FromCompositionTags(metrics, current, &namespaces) } for _, namespace := range namespaces { mts = append(mts, plugin.MetricType{ Namespace_: core.NewNamespace(strings.Split(namespace, "/")...), Config_: cfg.ConfigDataNode, }) } return mts, nil }
// CollectMetrics retrieves values of requested metrics func (d *docker) CollectMetrics(mts []plugin.MetricType) ([]plugin.MetricType, error) { var err error metrics := []plugin.MetricType{} d.list = map[string]dock.APIContainers{} // get list of possible network metrics networkMetrics := []string{} utils.FromCompositionTags(wrapper.NetworkInterface{}, "", &networkMetrics) // get list of all running containers d.list, err = d.client.ListContainersAsMap() if err != nil { fmt.Fprintln(os.Stderr, "The list of running containers cannot be retrived, err=", err) return nil, err } // retrieve requested docker ids rids, err := d.getRequestedIDs(mts...) if err != nil { return nil, err } // for each requested id set adequate item into docker.container struct with stats for _, rid := range rids { if contSpec, exist := d.list[rid]; !exist { return nil, fmt.Errorf("Docker container does not exist, container_id=%s", rid) } else { stats, err := d.client.GetStatsFromContainer(contSpec.ID, true) if err != nil { return nil, err } // set new item to docker.container structure d.containers[rid] = containerData{ ID: contSpec.ID, Info: wrapper.Specification{ Status: contSpec.Status, Created: time.Unix(contSpec.Created, 0).Format("2006-01-02T15:04:05Z07:00"), Image: contSpec.Image, SizeRw: contSpec.SizeRw, SizeRootFs: contSpec.SizeRootFs, Labels: contSpec.Labels, }, Stats: stats, } } } for _, mt := range mts { ids, err := d.getRequestedIDs(mt) if err != nil { return nil, err } for _, id := range ids { ns := make([]core.NamespaceElement, len(mt.Namespace())) copy(ns, mt.Namespace()) ns[2].Value = id // omit "spec" metrics for root if id == "root" && mt.Namespace()[lengthOfNsPrefix].Value == "spec" { continue } isDynamic, indexes := mt.Namespace()[lengthOfNsPrefix:].IsDynamic() metricName := mt.Namespace().Strings()[lengthOfNsPrefix:] // remove added static element (`value`) if metricName[len(metricName)-1] == "value" { metricName = metricName[:len(metricName)-1] } if !isDynamic { metric := plugin.MetricType{ Timestamp_: time.Now(), Namespace_: ns, Data_: utils.GetValueByNamespace(d.containers[id], metricName), Tags_: mt.Tags(), Config_: mt.Config(), Version_: VERSION, } metrics = append(metrics, metric) continue } // take the element of metricName which precedes the first dynamic element // e.g. {"filesystem", "*", "usage"} // -> statsType will be "filesystem", // -> scope of metricName will be decreased to {"*", "usage"} indexOfDynamicElement := indexes[0] statsType := metricName[indexOfDynamicElement-1] metricName = metricName[indexOfDynamicElement:] switch statsType { case "filesystem": // get docker filesystem statistics devices := []string{} if metricName[0] == "*" { // when device name is requested as as asterisk - take all available filesystem devices for deviceName := range d.containers[id].Stats.Filesystem { devices = append(devices, deviceName) } } else { // device name is requested explicitly device := metricName[0] if _, ok := d.containers[id].Stats.Filesystem[device]; !ok { return nil, fmt.Errorf("In metric %s the given device name is invalid (no stats for this device)", mt.Namespace().String()) } devices = append(devices, metricName[0]) } for _, device := range devices { rns := make([]core.NamespaceElement, len(ns)) copy(rns, ns) rns[indexOfDynamicElement+lengthOfNsPrefix].Value = device metric := plugin.MetricType{ Timestamp_: time.Now(), Namespace_: rns, Data_: utils.GetValueByNamespace(d.containers[id].Stats.Filesystem[device], metricName[1:]), Tags_: mt.Tags(), Config_: mt.Config(), Version_: VERSION, } metrics = append(metrics, metric) } case "labels": // get docker labels labelKeys := []string{} if metricName[0] == "*" { // when label key is requested as an asterisk - take all available labels for labelKey := range d.containers[id].Info.Labels { labelKeys = append(labelKeys, labelKey) } } else { labelKey := metricName[0] if _, ok := d.containers[id].Info.Labels[labelKey]; !ok { return nil, fmt.Errorf("In metric %s the given label is invalid (no value for this label key)", mt.Namespace().String()) } labelKeys = append(labelKeys, metricName[0]) } for _, labelKey := range labelKeys { rns := make([]core.NamespaceElement, len(ns)) copy(rns, ns) rns[indexOfDynamicElement+lengthOfNsPrefix].Value = utils.ReplaceNotAllowedCharsInNamespacePart(labelKey) metric := plugin.MetricType{ Timestamp_: time.Now(), Namespace_: rns, Data_: d.containers[id].Info.Labels[labelKey], Tags_: mt.Tags(), Config_: mt.Config(), Version_: VERSION, } metrics = append(metrics, metric) } case "network": //get docker network tx/rx statistics netInterfaces := []string{} ifaceMap := map[string]wrapper.NetworkInterface{} for _, iface := range d.containers[id].Stats.Network { ifaceMap[iface.Name] = iface } // support wildcard on interface name if metricName[0] == "*" { for _, netInterface := range d.containers[id].Stats.Network { netInterfaces = append(netInterfaces, netInterface.Name) } } else { netInterface := metricName[0] if _, ok := ifaceMap[netInterface]; !ok { return nil, fmt.Errorf("In metric %s the given network interface is invalid (no stats for this net interface)", mt.Namespace().String()) } netInterfaces = append(netInterfaces, metricName[0]) } for _, ifaceName := range netInterfaces { rns := make([]core.NamespaceElement, len(ns)) copy(rns, ns) rns[indexOfDynamicElement+lengthOfNsPrefix].Value = ifaceName metric := plugin.MetricType{ Timestamp_: time.Now(), Namespace_: rns, Data_: utils.GetValueByNamespace(ifaceMap[ifaceName], metricName[1:]), Tags_: mt.Tags(), Config_: mt.Config(), Version_: VERSION, } metrics = append(metrics, metric) } case "percpu_usage": numOfCPUs := len(d.containers[id].Stats.CgroupStats.CpuStats.CpuUsage.PercpuUsage) - 1 if metricName[0] == "*" { // when cpu ID is requested as an asterisk - take all available for cpuID, val := range d.containers[id].Stats.CgroupStats.CpuStats.CpuUsage.PercpuUsage { rns := make([]core.NamespaceElement, len(ns)) copy(rns, ns) rns[indexOfDynamicElement+lengthOfNsPrefix].Value = strconv.Itoa(cpuID) metric := plugin.MetricType{ Timestamp_: time.Now(), Namespace_: rns, Data_: val, Tags_: mt.Tags(), Config_: mt.Config(), Version_: VERSION, } metrics = append(metrics, metric) } } else { cpuID, err := strconv.Atoi(metricName[0]) if err != nil { return nil, fmt.Errorf("In metric %s the given cpu id is invalid, err=%v", mt.Namespace().String(), err) } if cpuID > numOfCPUs || cpuID < 0 { return nil, fmt.Errorf("In metric %s the given cpu id is invalid, expected value in range 0-%d", mt.Namespace().String(), numOfCPUs) } metric := plugin.MetricType{ Timestamp_: time.Now(), Namespace_: ns, Data_: d.containers[id].Stats.CgroupStats.CpuStats.CpuUsage.PercpuUsage[cpuID], Tags_: mt.Tags(), Config_: mt.Config(), Version_: VERSION, } metrics = append(metrics, metric) } } // the end of switch statsType } // the end of range over ids } if len(metrics) == 0 { return nil, errors.New("No metric found") } return metrics, nil }
func getListOfNetworkMetrics() []string { metrics := []string{} utils.FromCompositionTags(wrapper.NetworkInterface{}, "", &metrics) return metrics }